{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!--BOOK_INFORMATION-->\n",
    "<img align=\"left\" style=\"padding-right:10px;\" src=\"figures/DLlogosmall.png\">\n",
    "\n",
    "*This notebook contains an excerpt from the [Deep Learning with Tensorflow 2.0](https://www.adhiraiyan.org/DeepLearningWithTensorflow.html) by Mukesh Mithrakumar. The code is released under the [MIT license](https://opensource.org/licenses/MIT) and is available for FREE [on GitHub](https://github.com/adhiraiyan/DeepLearningWithTF2.0).*\n",
    "\n",
    "*Open Source runs on love, laughter and a whole lot of coffee. Consider buying me [one](https://www.buymeacoffee.com/mmukesh) if you find this content useful!*\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!--NAVIGATION-->\n",
    "< [01.00 - Preface](01.00-Introduction.ipynb) | [Contents](Index.ipynb) | [03.00 - Probability and Information Theory](03.00-Probability-and-Information-Theory.ipynb) >\n",
    "\n",
    "<a href=\"https://colab.research.google.com/github/adhiraiyan/DeepLearningWithTF2.0/blob/master/notebooks/02.00-Linear-Algebra.ipynb\"><img align=\"left\" src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open in Colab\" title=\"Open and Execute in Google Colaboratory\"></a>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "yeA6xblhq8w8"
   },
   "source": [
    "# 02.00 - Linear Algebra"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "F-3yQvOQHjLr"
   },
   "source": [
    "Linear algebra is the branch of mathematics concerning linear equations and linear functions and their representations through matrices and vector spaces.\n",
    "\n",
    "Machine Learning relies heavily on Linear Algebra, so it is essential to understand what vectors and matrices are, what operations you can perform with them, and how they can be useful.\n",
    "\n",
    "If you are already familiar with linear algebra, feel free to skip this chapter but note that the implementation of certain functions are different between Tensorflow 1.0 and Tensorflow 2.0 so you should atleast skim through the code.\n",
    "\n",
    "If you have had no exposure at all to linear algebra, this chapter will teach you enough to read this book."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 85
    },
    "colab_type": "code",
    "id": "4cvzagFXgQrd",
    "outputId": "fc155a91-c258-47e4-bfa6-1607b2d2e484"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[K     |████████████████████████████████| 79.9MB 1.8MB/s \n",
      "\u001b[K     |████████████████████████████████| 3.0MB 49.7MB/s \n",
      "\u001b[K     |████████████████████████████████| 419kB 53.2MB/s \n",
      "\u001b[K     |████████████████████████████████| 61kB 29.2MB/s \n",
      "\u001b[?25h"
     ]
    }
   ],
   "source": [
    "# Installs\n",
    "!pip install --upgrade tf-nightly-2.0-preview"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "of02mDcgUuHQ"
   },
   "outputs": [],
   "source": [
    "# Imports\n",
    "import tensorflow as tf\n",
    "import sys\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "\n",
    "\"\"\"\n",
    "If you are running this notebook in Google colab, make sure to upload the helpers.py file to your \n",
    "session before running it, but if you are running this in Binder, then you \n",
    "don't have to worry about it. The helpers.py file will be in the notebook\n",
    "folder in GitHub.\n",
    "\n",
    "\"\"\"\n",
    "from helpers import vector_plot, plot_transform"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "JBpOlp4jrAoy"
   },
   "source": [
    "# 02.01 - Scalars, Vectors, Matrices and Tensors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "kWXNG-cmHrih"
   },
   "source": [
    "__Scalars:__ are just a single number. For example temperature, which is denoted by just one number.\n",
    "\n",
    "\n",
    "__Vectors:__ are an array of numbers. The numbers are arranged in order and we can identify each individual number by its index in that ordering. We can think of vectors as identifying points in space, with each element giving the coordinate along a different axis. In simple terms, a vector is an arrow representing a quantity that has both magnitude and direction wherein the length of the arrow represents the magnitude and the orientation tells you the direction. For example wind, which has a direction and magnitude.\n",
    "\n",
    "__Matrices:__ A matrix is a 2D-array of numbers, so each element is identified by two indices instead of just one. If a real valued matrix $A$ has a height of *m* and a width of *n*, then we say that $A \\in \\mathbb{R}^{m \\times n}$. We identify the elements of the matrix as $A_{m,n}$ where *m* represents the row and *n* represents the column.\n",
    "\n",
    "![Scalars, Vectors, Matrices and Tensors](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0201a.png)\n",
    "\n",
    "__Tensors:__ In the general case, are an array of numbers arranged on a regular grid with a variable number of axes is knows as a tensor. We identify the elements of a tensor $A$ at coordinates(*i, j, k*) by writing $A_{i, j, k}$. But to truly understand tensors, we need to expand the way we think of vectors as only arrows with a magnitude and direction. Remember that a vector can be represented by three components, namely the x, y and z components (basis vectors). If you have a pen and a paper, let's do a small experiment, place the pen vertically on the paper and slant it by some angle and now shine a light from top such that the shadow of the pen falls on the paper, this shadow, represents the x component of the vector \"pen\" and the height from the paper to the tip of the pen is the y component. Now, let's take these components to describe tensors, imagine, you are Indiana Jones or a treasure hunter and you are trapped in a cube and there are three arrows flying towards you from the three faces (to represent x, y, z axis) of the cube 😬, I know this will be the last thing you would think in such a situation but you can think of those three arrows as vectors pointing towards you from the three faces of the cube and you can represent those vectors (arrows) in x, y and z components, now that is a rank 2 tensor (matrix) with 9 components. Remember that this is a very very simple explanation of tensors. Following is a representation of a tensor:\n",
    "\n",
    "![Tensors](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0201b.PNG)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "HdIOKDSbNBaz"
   },
   "source": [
    "We can add matrices to each other as long as they have the same shape, just by adding their corresponding elements:\n",
    "\n",
    "$$\\color{orange}{C = A + B \\ where \\ C_{i,j} = A_{i,j} + B_{i,j} \\tag{1}}$$\n",
    "\n",
    "\n",
    "In tensorflow a:\n",
    "\n",
    "- Rank 0 Tensor is a Scalar\n",
    "- Rank 1 Tensor is a Vector\n",
    "- Rank 2 Tensor is a Matrix\n",
    "- Rank 3 Tensor is a 3-Tensor\n",
    "- Rank n Tensor is a n-Tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 255
    },
    "colab_type": "code",
    "id": "rKermF6KHs11",
    "outputId": "9964b9e9-5832-4bab-8e0e-446bf52a2026"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3x3 Rank 2 Tensor A: \n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "\n",
      "3x3 Rank 2 Tensor B: \n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]\n",
      " [7. 8. 9.]]\n",
      "\n",
      "Rank 2 Tensor C with shape=(3, 3) and elements: \n",
      "[[ 2.  3.  4.]\n",
      " [ 5.  6.  7.]\n",
      " [ 8.  9. 10.]]\n"
     ]
    }
   ],
   "source": [
    "# let's create a ones 3x3 rank 2 tensor\n",
    "rank_2_tensor_A = tf.ones([3, 3], name='MatrixA')\n",
    "print(\"3x3 Rank 2 Tensor A: \\n{}\\n\".format(rank_2_tensor_A))\n",
    "\n",
    "# let's manually create a 3x3 rank two tensor and specify the data type as float\n",
    "rank_2_tensor_B = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], name='MatrixB', dtype=tf.float32)\n",
    "print(\"3x3 Rank 2 Tensor B: \\n{}\\n\".format(rank_2_tensor_B))\n",
    "\n",
    "# addition of the two tensors\n",
    "rank_2_tensor_C = tf.add(rank_2_tensor_A, rank_2_tensor_B, name='MatrixC')\n",
    "print(\"Rank 2 Tensor C with shape={} and elements: \\n{}\".format(rank_2_tensor_C.shape, rank_2_tensor_C))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 71
    },
    "colab_type": "code",
    "id": "C1Fm6XkQaYcn",
    "outputId": "6575ea04-ebce-4b73-8d92-15a885289850"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Incompatible shapes to add with two_by_three of shape (2, 3) and 3x3 Rank 2 Tensor B of shape (3, 3)\n",
      "    \n"
     ]
    }
   ],
   "source": [
    "# Let's see what happens if the shapes are not the same\n",
    "two_by_three = tf.ones([2, 3])\n",
    "try:\n",
    "    incompatible_tensor = tf.add(two_by_three, rank_2_tensor_B)\n",
    "except:\n",
    "    print(\"\"\"Incompatible shapes to add with two_by_three of shape {0} and 3x3 Rank 2 Tensor B of shape {1}\n",
    "    \"\"\".format(two_by_three.shape, rank_2_tensor_B.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XuJ4rvIFNafT"
   },
   "source": [
    "We can also add a scalar to a matrix or multiply a matrix by a scalar, just by performing that operation on each element of a matrix:\n",
    "\n",
    "$$\\color{orange}{D = a \\cdot B + c \\ where  \\ D_{i,j} = a \\cdot B_{i,j} + c \\tag{2}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 306
    },
    "colab_type": "code",
    "id": "97sSpKcbTNoY",
    "outputId": "5546ce85-9392-4fc5-f6b1-a2443ef25103"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Rank 2 Tensor B: \n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]\n",
      " [7. 8. 9.]] \n",
      "\n",
      "Scalar a: 2.0\n",
      "Rank 2 Tensor for aB: \n",
      "[[ 2.  4.  6.]\n",
      " [ 8. 10. 12.]\n",
      " [14. 16. 18.]] \n",
      "\n",
      "Scalar c: 3.0 \n",
      "Rank 2 Tensor D = aB + c: \n",
      "[[ 5.  7.  9.]\n",
      " [11. 13. 15.]\n",
      " [17. 19. 21.]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Create scalar a, c and Matrix B\n",
    "rank_0_tensor_a = tf.constant(2, name=\"scalar_a\", dtype=tf.float32)\n",
    "rank_2_tensor_B = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], name='MatrixB', dtype=tf.float32)\n",
    "rank_0_tensor_c = tf.constant(3, name=\"scalar_c\", dtype=tf.float32)\n",
    "\n",
    "# multiplying aB\n",
    "multiply_scalar = tf.multiply(rank_0_tensor_a, rank_2_tensor_B)\n",
    "# adding aB + c\n",
    "rank_2_tensor_D = tf.add(multiply_scalar, rank_0_tensor_c, name=\"MatrixD\")\n",
    "\n",
    "print(\"\"\"Original Rank 2 Tensor B: \\n{0} \\n\\nScalar a: {1}\n",
    "Rank 2 Tensor for aB: \\n{2} \\n\\nScalar c: {3} \\nRank 2 Tensor D = aB + c: \\n{4}\n",
    "\"\"\".format(rank_2_tensor_B, rank_0_tensor_a, multiply_scalar, rank_0_tensor_c, rank_2_tensor_D))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "5uI2sKntMyj6"
   },
   "source": [
    "One important operation on matrices is the __transpose__. The transpose of a matrix is the mirror image of the martrix across a diagonal line, called the __main diagonal__. We denote the transpose of a matrix $A$ as $A^\\top$ and is defined as such: $(A^\\top)_{i, j} = A_{j, i}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 153
    },
    "colab_type": "code",
    "id": "pJwqOF1hzZNn",
    "outputId": "8edb6277-2eb5-40cb-e71a-f1276402d493"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rank 2 Tensor E of shape: (2, 3) and elements: \n",
      "[[1 2 3]\n",
      " [4 5 6]]\n",
      "\n",
      "Transpose of Rank 2 Tensor E of shape: (3, 2) and elements: \n",
      "[[1 4]\n",
      " [2 5]\n",
      " [3 6]]\n"
     ]
    }
   ],
   "source": [
    "# Creating a Matrix E\n",
    "rank_2_tensor_E = tf.constant([[1, 2, 3], [4, 5, 6]])\n",
    "# Transposing Matrix E\n",
    "transpose_E = tf.transpose(rank_2_tensor_E, name=\"transposeE\")\n",
    "\n",
    "print(\"\"\"Rank 2 Tensor E of shape: {0} and elements: \\n{1}\\n\n",
    "Transpose of Rank 2 Tensor E of shape: {2} and elements: \\n{3}\"\"\".format(rank_2_tensor_E.shape, rank_2_tensor_E, \n",
    "                                                                         transpose_E.shape, transpose_E))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "uU6F-99XM3_v"
   },
   "source": [
    "In deep learning we allow the addition of matrix and a vector, yielding another matrix where $C_{i, j} = A_{i, j} + b_{j}$. In other words, the vector $b$ is added to each row of the matrix. This implicit copying of $b$ to many locations is called __broadcasting__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 255
    },
    "colab_type": "code",
    "id": "v3s5ZyQKUSY6",
    "outputId": "9fa8acd7-910c-4a63-d538-a336e25ed839"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rank 2 tensor A: \n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      " \n",
      "Rank 1 Tensor b: \n",
      "[[4.]\n",
      " [5.]\n",
      " [6.]] \n",
      "\n",
      "Rank 2 tensor F = A + b:\n",
      "[[5. 5. 5.]\n",
      " [6. 6. 6.]\n",
      " [7. 7. 7.]]\n"
     ]
    }
   ],
   "source": [
    "# Creating a vector b\n",
    "rank_1_tensor_b = tf.constant([[4.], [5.], [6.]])\n",
    "# Broadcasting a vector b to a matrix A such that it yields a matrix F = A + b\n",
    "rank_2_tensor_F = tf.add(rank_2_tensor_A, rank_1_tensor_b, name=\"broadcastF\")\n",
    "\n",
    "print(\"\"\"Rank 2 tensor A: \\n{0}\\n \\nRank 1 Tensor b: \\n{1} \n",
    "\\nRank 2 tensor F = A + b:\\n{2}\"\"\".format(rank_2_tensor_A, rank_1_tensor_b, rank_2_tensor_F))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ziVxUA2WHyf4"
   },
   "source": [
    "# 02.02 - Multiplying Matrices and Vectors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "MzuhfYTyOR9N"
   },
   "source": [
    "To define the matrix product of matrices $A \\ \\text{and} \\ B, \\ A$ must have the same number of columns as $B$. If $A$ is of shape *m x n* and $B$ is of shape *n x p*, then $C$ is of shape *m x p*.\n",
    "\n",
    "$$\\color{orange}{C_{i, j} = \\displaystyle\\sum_k A_{i, k} B_{k, j} \\tag{3}}$$\n",
    "\n",
    "If you do not recall how matrix multiplication is performed, take a look at:\n",
    "\n",
    "![Multiplying Matrices](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0202a.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 272
    },
    "colab_type": "code",
    "id": "Rrv8DzWuHzWR",
    "outputId": "977d5ffd-3709-4924-d07e-459fed5ddb28"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: shape (2, 3) \n",
      "elements: \n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]] \n",
      "\n",
      "Matrix B: shape (3, 4) \n",
      "elements: \n",
      "[[1. 2. 3. 4.]\n",
      " [1. 2. 3. 4.]\n",
      " [1. 2. 3. 4.]]\n",
      "\n",
      "Matrix C: shape (2, 4) \n",
      "elements: \n",
      "[[ 3.  6.  9. 12.]\n",
      " [ 3.  6.  9. 12.]]\n"
     ]
    }
   ],
   "source": [
    "# Matrix A and B with shapes (2, 3) and (3, 4)\n",
    "mmv_matrix_A = tf.ones([2, 3], name=\"matrix_A\")\n",
    "mmv_matrix_B = tf.constant([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], name=\"matrix_B\", dtype=tf.float32)\n",
    "\n",
    "# Matrix Multiplication: C = AB with C shape (2, 4)\n",
    "matrix_multiply_C = tf.matmul(mmv_matrix_A, mmv_matrix_B, name=\"matrix_multiply_C\")\n",
    "\n",
    "print(\"\"\"Matrix A: shape {0} \\nelements: \\n{1} \\n\\nMatrix B: shape {2} \\nelements: \\n{3}\n",
    "\\nMatrix C: shape {4} \\nelements: \\n{5}\"\"\".format(mmv_matrix_A.shape, mmv_matrix_A, mmv_matrix_B.shape, \n",
    "                                                  mmv_matrix_B, matrix_multiply_C.shape, matrix_multiply_C))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "pz_vETHqOXpL"
   },
   "source": [
    "To get a matrix containing the product of the individual elements, we use __element wise product__ or __Hadamard product__ and is denoted as $A \\odot B$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 306
    },
    "colab_type": "code",
    "id": "mvYdsHcoahkl",
    "outputId": "b72763c3-2623-41c9-c733-838ec65845af"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: shape (3, 3) \n",
      "elements: \n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]] \n",
      "\n",
      "Matrix A: shape (3, 3) \n",
      "elements: \n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]\n",
      " [7. 8. 9.]]\n",
      "\n",
      "Matrix C: shape (3, 3) \n",
      "elements: \n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]\n",
      " [7. 8. 9.]]\n"
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "Note that we use multiply to do element wise matrix multiplication and matmul\n",
    "to do matrix multiplication\n",
    "\"\"\"\n",
    "# Creating new Matrix A and B with shapes (3, 3)\n",
    "element_matrix_A = tf.ones([3, 3], name=\"element_matrix_A\")\n",
    "element_matrix_B = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], name=\"element_matrix_B\", dtype=tf.float32)\n",
    "\n",
    "# Element wise multiplication of Matrix A and B\n",
    "element_wise_C = tf.multiply(element_matrix_A, element_matrix_B, name=\"element_wise_C\")\n",
    "\n",
    "print(\"\"\"Matrix A: shape {0} \\nelements: \\n{1} \\n\\nMatrix A: shape {2} \\nelements: \\n{3}\\n\n",
    "Matrix C: shape {4} \\nelements: \\n{5}\"\"\".format(element_matrix_A.shape, element_matrix_A, element_matrix_B.shape, \n",
    "                                                element_matrix_B, element_wise_C.shape, element_wise_C))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "qSDm2ptGObdH"
   },
   "source": [
    "To compute the dot product between $A$ and $B$ we compute $C_{i, j}$ as the dot product between row *i* of $A$ and column *j* of $B$.\n",
    "\n",
    "![Dot Product](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0202b.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 306
    },
    "colab_type": "code",
    "id": "gLAOBe-DahA3",
    "outputId": "6ae165de-a8ee-43ed-b181-d6bca0a6ab73"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: shape (3, 3) \n",
      "elements: \n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]] \n",
      "\n",
      "Matrix B: shape (3, 3) \n",
      "elements: \n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]\n",
      " [7. 8. 9.]]\n",
      "\n",
      "Matrix C: shape (3, 3) \n",
      "elements: \n",
      "[[12. 15. 18.]\n",
      " [12. 15. 18.]\n",
      " [12. 15. 18.]]\n"
     ]
    }
   ],
   "source": [
    "# Creating Matrix A and B with shapes (3, 3)\n",
    "dot_matrix_A = tf.ones([3, 3], name=\"dot_matrix_A\")\n",
    "dot_matrix_B = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], name=\"dot_matrix_B\", dtype=tf.float32)\n",
    "\n",
    "# Dot product of A and B\n",
    "dot_product_C = tf.tensordot(dot_matrix_A, dot_matrix_B, axes=1, name=\"dot_product_C\")\n",
    "\n",
    "print(\"\"\"Matrix A: shape {0} \\nelements: \\n{1} \\n\\nMatrix B: shape {2} \\nelements: \\n{3}\\n\n",
    "Matrix C: shape {4} \\nelements: \\n{5}\"\"\".format(dot_matrix_A.shape, dot_matrix_A, dot_matrix_B.shape, \n",
    "                                                dot_matrix_B, dot_product_C.shape, dot_product_C))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6jzMm9CjOgrR"
   },
   "source": [
    "\n",
    "Some properties of matrix multiplication (Distributive property):\n",
    "\n",
    "$$\\color{orange}{A(B +C) = AB + AC \\tag{4}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "2a3T93aNlEcr"
   },
   "outputs": [],
   "source": [
    "# Common Matrices to check all the matrix Properties\n",
    "matrix_A = tf.constant([[1, 2], [3, 4]], name=\"matrix_a\")\n",
    "matrix_B = tf.constant([[5, 6], [7, 8]], name=\"matrix_b\")\n",
    "matrix_C = tf.constant([[9, 1], [2, 3]], name=\"matrix_c\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 357
    },
    "colab_type": "code",
    "id": "QTryvCNYcDSD",
    "outputId": "0b9bb5fc-d4de-4e77-cbb8-5d651bc6457d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[1 2]\n",
      " [3 4]] \n",
      "\n",
      "Matrix B: \n",
      "[[5 6]\n",
      " [7 8]] \n",
      "\n",
      "Matrix C: \n",
      "[[9 1]\n",
      " [2 3]]\n",
      "\n",
      "Distributive property is valid\n",
      "RHS: AB + AC: \n",
      "[[32 29]\n",
      " [78 65]] \n",
      "\n",
      "LHS: A(B+C): \n",
      "[[32 29]\n",
      " [78 65]]\n"
     ]
    }
   ],
   "source": [
    "# Distributive Property\n",
    "print(\"Matrix A: \\n{} \\n\\nMatrix B: \\n{} \\n\\nMatrix C: \\n{}\\n\".format(matrix_A, matrix_B, matrix_C))\n",
    "\n",
    "# AB + AC\n",
    "distributive_RHS = tf.add(tf.matmul(matrix_A, matrix_B), tf.matmul(matrix_A, matrix_C), name=\"RHS\")\n",
    "\n",
    "# A(B+C)\n",
    "distributive_LHS = tf.matmul(matrix_A, (tf.add(matrix_B, matrix_C)), name=\"LHS\")\n",
    "\n",
    "\"\"\"\n",
    "Following is another way a conditional statement can be implemented from tensorflow\n",
    "This might not seem very useful now but I want to introduce it here so you can\n",
    "figure out how it works for a simple example.\n",
    "\"\"\"\n",
    "# To compare each element in the matrix, you need to reduce it first and check if it's equal\n",
    "predictor = tf.reduce_all(tf.equal(distributive_RHS, distributive_LHS))\n",
    "\n",
    "# condition to act on if predictor is True\n",
    "def true_print(): print(\"\"\"Distributive property is valid\n",
    "RHS: AB + AC: \\n{} \\n\\nLHS: A(B+C): \\n{}\"\"\".format(distributive_RHS, distributive_LHS))\n",
    "    \n",
    "# condition to act on if predictor is False    \n",
    "def false_print(): print(\"\"\"You Broke the Distributive Property of Matrix\n",
    "RHS: AB + AC: \\n{} \\n\\nis NOT Equal to LHS: A(B+C): \\n{}\"\"\".format(distributive_RHS, distributive_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "TkSyrN_uOkUC"
   },
   "source": [
    "Some properties of matrix multiplication (Associative property):\n",
    "\n",
    "$$\\color{orange}{A(BC) = (AB)C \\tag{5}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 357
    },
    "colab_type": "code",
    "id": "jV9y4WC8cDJ5",
    "outputId": "c350ff88-e589-4ece-e707-9d70f5bd5175"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[1 2]\n",
      " [3 4]] \n",
      "\n",
      "Matrix B: \n",
      "[[5 6]\n",
      " [7 8]] \n",
      "\n",
      "Matrix C: \n",
      "[[9 1]\n",
      " [2 3]]\n",
      "\n",
      "Associative property is valid\n",
      "RHS: (AB)C: \n",
      "[[215  85]\n",
      " [487 193]] \n",
      "\n",
      "LHS: A(BC): \n",
      "[[215  85]\n",
      " [487 193]]\n"
     ]
    }
   ],
   "source": [
    "# Associative property\n",
    "print(\"Matrix A: \\n{} \\n\\nMatrix B: \\n{} \\n\\nMatrix C: \\n{}\\n\".format(matrix_A, matrix_B, matrix_C))\n",
    "\n",
    "# (AB)C\n",
    "associative_RHS = tf.matmul(tf.matmul(matrix_A, matrix_B), matrix_C)\n",
    "\n",
    "# A(BC)\n",
    "associative_LHS = tf.matmul(matrix_A, tf.matmul(matrix_B, matrix_C))\n",
    "\n",
    "# To compare each element in the matrix, you need to reduce it first and check if it's equal\n",
    "predictor = tf.reduce_all(tf.equal(associative_RHS, associative_LHS))\n",
    "\n",
    "# condition to act on if predictor is True\n",
    "def true_print(): print(\"\"\"Associative property is valid\n",
    "RHS: (AB)C: \\n{} \\n\\nLHS: A(BC): \\n{}\"\"\".format(associative_RHS, associative_LHS))\n",
    "\n",
    "# condition to act on if predictor is False    \n",
    "def false_print(): print(\"\"\"You Broke the Associative Property of Matrix\n",
    "RHS: (AB)C: \\n{} \\n\\nLHS: A(BC): \\n{}\"\"\".format(associative_RHS, associative_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "hOiY8XZtOoap"
   },
   "source": [
    "Some properties of matrix multiplication (Matrix multiplication is not commutative):\n",
    "\n",
    "$$\\color{orange}{AB \\neq BA \\tag{6}}$$ "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 289
    },
    "colab_type": "code",
    "id": "6Pv8SOxbcC-U",
    "outputId": "5414ccad-1e67-4813-eb76-56407c93ee32"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[1 2]\n",
      " [3 4]] \n",
      "\n",
      "Matrix B: \n",
      "[[5 6]\n",
      " [7 8]]\n",
      "\n",
      "Matrix Multiplication is not commutative\n",
      "RHS: (AB): \n",
      "[[19 22]\n",
      " [43 50]] \n",
      "\n",
      "LHS: (BA): \n",
      "[[23 34]\n",
      " [31 46]]\n"
     ]
    }
   ],
   "source": [
    "# Matrix multiplication is not commutative\n",
    "print(\"Matrix A: \\n{} \\n\\nMatrix B: \\n{}\\n\".format(matrix_A, matrix_B))\n",
    "\n",
    "# Matrix A times B\n",
    "commutative_RHS = tf.matmul(matrix_A, matrix_B)\n",
    "\n",
    "# Matrix B times A\n",
    "commutative_LHS = tf.matmul(matrix_B, matrix_A)\n",
    "\n",
    "predictor = tf.logical_not(tf.reduce_all(tf.equal(commutative_RHS, commutative_LHS)))\n",
    "def true_print(): print(\"\"\"Matrix Multiplication is not commutative\n",
    "RHS: (AB): \\n{} \\n\\nLHS: (BA): \\n{}\"\"\".format(commutative_RHS, commutative_LHS))\n",
    "\n",
    "def false_print(): print(\"\"\"You made Matrix Multiplication commutative\n",
    "RHS: (AB): \\n{} \\n\\nLHS: (BA): \\n{}\"\"\".format(commutative_RHS, commutative_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "j1Voc_AgOtIM"
   },
   "source": [
    "Some properties of matrix multiplication (Transpose):\n",
    "\n",
    "$$\\color{orange}{(AB)^\\top = B^{\\top} A^{\\top} \\tag{7}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 289
    },
    "colab_type": "code",
    "id": "Gz7cm3obcCZe",
    "outputId": "60e4938e-349e-44ac-ed60-fa43bd0fd364"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[1 2]\n",
      " [3 4]] \n",
      "\n",
      "Matrix B: \n",
      "[[5 6]\n",
      " [7 8]]\n",
      "\n",
      "Transpose property is valid\n",
      "RHS: (AB):^T \n",
      "[[19 43]\n",
      " [22 50]] \n",
      "\n",
      "LHS: (B^T A^T): \n",
      "[[19 43]\n",
      " [22 50]]\n"
     ]
    }
   ],
   "source": [
    "# Transpose of a matrix\n",
    "print(\"Matrix A: \\n{} \\n\\nMatrix B: \\n{}\\n\".format(matrix_A, matrix_B))\n",
    "\n",
    "# Tensorflow transpose function\n",
    "transpose_RHS = tf.transpose(tf.matmul(matrix_A, matrix_B))\n",
    "\n",
    "# If you are doing matrix multiplication tf.matmul has a parameter to take the tranpose and then matrix multiply\n",
    "transpose_LHS = tf.matmul(matrix_B, matrix_A, transpose_a=True, transpose_b=True)\n",
    "    \n",
    "predictor = tf.reduce_all(tf.equal(transpose_RHS, transpose_LHS))\n",
    "def true_print(): print(\"\"\"Transpose property is valid\n",
    "RHS: (AB):^T \\n{} \\n\\nLHS: (B^T A^T): \\n{}\"\"\".format(transpose_RHS, transpose_LHS))\n",
    "\n",
    "def false_print(): print(\"\"\"You Broke the Transpose Property of Matrix\n",
    "RHS: (AB):^T \\n{} \\n\\nLHS: (B^T A^T): \\n{}\"\"\".format(transpose_RHS, transpose_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "sltlInVEH1aU"
   },
   "source": [
    "# 02.03 - Identity and Inverse Matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Xg8Y9m_N3miE"
   },
   "source": [
    "Linear algebra offers a powerful tool called __matrix inversion__ that enables us to analytically solve $Ax = b$ for many values of $A$.\n",
    "\n",
    "To describe matrix inversion, we first need to define the concept of an __identity matrix__. An identity matrix is a matrix that does not change any vector when we multiply that vector by that matrix. \n",
    "\n",
    "Such that:\n",
    "\n",
    "$$\\color{orange}{I_n \\in \\mathbb{R}^{n \\times n} \\ \\text{and} \\ \\forall x \\in \\mathbb{R}^n, I_n x = x  \\tag{8}}$$\n",
    "\n",
    "The structure of the identity matrix is simple: all the entries along the main diagonal are 1, while all the other entries are zero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 255
    },
    "colab_type": "code",
    "id": "hDED6gFiH4U9",
    "outputId": "c0c6ae20-c832-4431-dcbd-f859c7fa4994"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Identity matrix I: \n",
      "[[1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [0. 0. 1.]]\n",
      "\n",
      "Vector x: \n",
      "[[4.]\n",
      " [5.]\n",
      " [6.]]\n",
      "\n",
      "Matrix C from Ix: \n",
      "[[4.]\n",
      " [5.]\n",
      " [6.]]\n"
     ]
    }
   ],
   "source": [
    "# let's create a identity matrix I\n",
    "identity_matrix_I = tf.eye(3, 3, dtype=tf.float32, name='IdentityMatrixI')\n",
    "print(\"Identity matrix I: \\n{}\\n\".format(identity_matrix_I))\n",
    "\n",
    "# let's create a 3x1 vector x\n",
    "iim_vector_x = tf.constant([[4], [5], [6]], name='Vector_x', dtype=tf.float32)\n",
    "print(\"Vector x: \\n{}\\n\".format(iim_vector_x))\n",
    "\n",
    "# Ix will result in x\n",
    "iim_matrix_C = tf.matmul(identity_matrix_I, iim_vector_x, name='MatrixC')\n",
    "print(\"Matrix C from Ix: \\n{}\".format(iim_matrix_C))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "44tX7v9F4Lbv"
   },
   "source": [
    "The __matrix inverse__ of $A$ is denoted as $A^{-1}$, and it is defined as the matrix such that:\n",
    "\n",
    "$$\\color{orange}{A^{-1} A = I_n \\tag{9}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 289
    },
    "colab_type": "code",
    "id": "f6RR6gQB9bTM",
    "outputId": "9d83b6df-27c9-4268-fed0-f28ada5972f7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A^-1 times A equals the Identity Matrix\n",
      "Matrix A: \n",
      "[[2. 3.]\n",
      " [2. 2.]] \n",
      "\n",
      "Inverse of Matrix A: \n",
      "[[-1.   1.5]\n",
      " [ 1.  -1. ]] \n",
      "\n",
      "RHS: I: \n",
      "[[1. 0.]\n",
      " [0. 1.]] \n",
      "\n",
      "LHS: A^(-1) A: \n",
      "[[1. 0.]\n",
      " [0. 1.]]\n"
     ]
    }
   ],
   "source": [
    "iim_matrix_A = tf.constant([[2, 3], [2, 2]], name='MatrixA', dtype=tf.float32)\n",
    "\n",
    "try:\n",
    "    # Tensorflow function to take the inverse\n",
    "    inverse_matrix_A = tf.linalg.inv(iim_matrix_A)\n",
    "    \n",
    "    # Creating a identity matrix using tf.eye\n",
    "    identity_matrix = tf.eye(2, 2, dtype=tf.float32, name=\"identity\")\n",
    "\n",
    "    iim_RHS = identity_matrix\n",
    "    iim_LHS = tf.matmul(inverse_matrix_A, iim_matrix_A, name=\"LHS\")\n",
    "    \n",
    "    predictor = tf.reduce_all(tf.equal(iim_RHS, iim_LHS))\n",
    "    def true_print(): print(\"\"\"A^-1 times A equals the Identity Matrix\n",
    "Matrix A: \\n{0} \\n\\nInverse of Matrix A: \\n{1} \\n\\nRHS: I: \\n{2} \\n\\nLHS: A^(-1) A: \\n{3}\"\"\".format(iim_matrix_A,\n",
    "                                                                                                    inverse_matrix_A,\n",
    "                                                                                                    iim_RHS, \n",
    "                                                                                                    iim_LHS))\n",
    "    def false_print(): print(\"Condition Failed\")\n",
    "    tf.cond(predictor, true_print, false_print)\n",
    "    \n",
    "except:\n",
    "    print(\"\"\"A^-1 doesnt exist\n",
    "    Matrix A: \\n{} \\n\\nInverse of Matrix A: \\n{} \\n\\nRHS: I: \\n{} \n",
    "    \\nLHS: (A^(-1) A): \\n{}\"\"\".format(iim_matrix_A, inverse_matrix_A, iim_RHS, iim_LHS))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1CAGKbsr9yZv"
   },
   "source": [
    "If you try different values for Matrix A, you will see that, not all $A$ has an inverse and we will discuss the conditions for the existence of $A^{-1}$ in the following section.\n",
    "\n",
    "We can then solve the equation $Ax = b$ as:\n",
    "\n",
    "$$\n",
    "\\color{Orange}{A^{-1} Ax = A^{-1} b} \\\\\n",
    "\\color{Orange}{I_n x = A^{-1} b} \\\\\n",
    "\\color{Orange}{x  =A^{-1} b \\tag{10}}\n",
    "$$\n",
    "\n",
    "This process depends on it being possible to find $A^{-1}$. \n",
    "\n",
    "We can calculate the inverse of a matrix by:\n",
    "\n",
    "![Matrix Inverse](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0203a.PNG)\n",
    "\n",
    "Lets see how we can solve a simple linear equation: 2x + 3y = 6 and 4x + 9y = 15"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 221
    },
    "colab_type": "code",
    "id": "sizO9Oz66KpD",
    "outputId": "75428b1c-3b0f-476e-a521-7d6baf988d47"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[2. 3.]\n",
      " [4. 9.]] \n",
      "\n",
      "Vector B: \n",
      "[[ 6.]\n",
      " [15.]]\n",
      "\n",
      "Vector x is: \n",
      "[[1.5]\n",
      " [1. ]] \n",
      "Where x = [1.5] and y = [1.]\n"
     ]
    }
   ],
   "source": [
    "# The above system of equation can be written in the matrix format as:\n",
    "sys_matrix_A = tf.constant([[2, 3], [4, 9]], dtype=tf.float32)\n",
    "sys_vector_B = tf.constant([[6], [15]], dtype=tf.float32)\n",
    "print(\"Matrix A: \\n{} \\n\\nVector B: \\n{}\\n\".format(sys_matrix_A, sys_vector_B))\n",
    "\n",
    "# now to solve for x: x = A^(-1)b\n",
    "sys_x = tf.matmul(tf.linalg.inv(sys_matrix_A), sys_vector_B)\n",
    "print(\"Vector x is: \\n{} \\nWhere x = {} and y = {}\".format(sys_x, sys_x[0], sys_x[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "CrDWCbU5H6ym"
   },
   "source": [
    "# 02.04 - Linear Dependence and Span"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "3J_B7nao9De_"
   },
   "source": [
    "For $A^{-1}$ to exits, $Ax = b$ must have exactly one solution for every value of $b$. It is also possible for the system of equations to have no solutions or infinitely many solutions for some values of $b$. This is simply because we are dealing with linear systems and two lines can't cross more than once. So, they can either cross once, cross never, or have infinite crossing, meaning the two lines are superimposed.\n",
    "\n",
    "Hence if both $x$ and $y$ are solutions then:\n",
    "\n",
    "$z = \\alpha x + (1 - \\alpha)y$ is also a solution for any real $\\alpha$\n",
    "\n",
    "![Linear Dependence](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0204a.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "yhPzsL6JEHAu"
   },
   "source": [
    "\n",
    "The span of a set of vectors is the set of all linear combinations of the vectors. Formally, a __linear combination__ of some set of vectors \n",
    "$\\{ v^1, \\cdots, v^n\\}$ is given by multiplying each vecor $v^{(i)}$ by a corresponding scalar coefficient and adding the results:\n",
    "\n",
    "$$\\color{Orange}{\\displaystyle\\sum_i c_i v^{(i)} \\tag{11}}$$\n",
    "\n",
    "Determining whether $Ax = b$ has a solution thus amounts to testing whether $b$ is in the span of the columns of $A$. This particular span is known as the __column space__ or the __range__, of $A$. \n",
    "\n",
    "In order for the system $Ax = b$ to have a solution for all values of $b \\in \\mathbb{R}^m$, we require that the column space of $A$ be all of $\\mathbb{R}^m$.\n",
    "\n",
    "A set of vectors $\\{ v^1, \\cdots, v^n\\}$ is __linearly independent__ if the only solution to the vector equation $\\lambda_1 v^1 + \\cdots \\lambda_n v^n = 0 \\ \\text{is} \\ \\lambda_i=0 \\ \\forall  i$. If a set of vectors is not linearly independent, then it is __linearly dependent__.\n",
    "\n",
    "For the matrix to have an inverse, the matrix must be __square__, that is, we require that *m = n* and that all the columns be linearly independent. A square matrix with linearly dependent columns is known as __singular__.\n",
    "\n",
    "If $A$ is not square or is square but singular, solving the equation is still possible, but we cannot use the method of matrix inversion to find the solution.\n",
    "\n",
    "So far we have discussed matrix inverses as being multiplied on the left. It is also possible to define an inverse that is multiplied on the right. For square matrixes, the left inverse and right inverse are equal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 272
    },
    "colab_type": "code",
    "id": "QsZ8dcQgWc6M",
    "outputId": "048b082a-109a-44da-daa6-6d850329b11d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A is successfully inverted: \n",
      "[[ 0.40000004 -0.20000002]\n",
      " [-0.20000002  0.6       ]]\n",
      "\n",
      "The two x values match, we proved that if a matrix A is invertible\n",
      "Then x = A^T b, \n",
      "where x: [2. 3.], \n",
      "\n",
      "A^T: \n",
      "[[ 0.40000004 -0.20000002]\n",
      " [-0.20000002  0.6       ]], \n",
      "\n",
      "b: \n",
      "[[6. 3.]\n",
      " [2. 6.]]\n"
     ]
    }
   ],
   "source": [
    "# Lets start by finding for some value of A and x, what the result of x is\n",
    "lds_matrix_A = tf.constant([[3, 1], [1, 2]], name='MatrixA', dtype=tf.float32)\n",
    "lds_vector_x = tf.constant([2, 3], name='vectorX', dtype=tf.float32)\n",
    "lds_b = tf.multiply(lds_matrix_A, lds_vector_x, name=\"b\")\n",
    "\n",
    "# Now let's see if an inverse for Matrix A exists\n",
    "try:\n",
    "    inverse_A = tf.linalg.inv(lds_matrix_A)\n",
    "    print(\"Matrix A is successfully inverted: \\n{}\".format(inverse_A))\n",
    "except:\n",
    "    print(\"Inverse of Matrix A: \\n{} \\ndoesn't exist. \".format(lds_matrix_A))\n",
    "\n",
    "# Let's find the value of x using x = A^(-1)b\n",
    "verify_x = tf.matmul(inverse_A, lds_b, name=\"verifyX\")\n",
    "predictor = tf.equal(lds_vector_x[0], verify_x[0][0]) and tf.equal(lds_vector_x[1], verify_x[1][1])\n",
    "\n",
    "def true_print(): print(\"\"\"\\nThe two x values match, we proved that if a matrix A is invertible\n",
    "Then x = A^T b, \\nwhere x: {}, \\n\\nA^T: \\n{}, \\n\\nb: \\n{}\"\"\".format(lds_vector_x, inverse_A, lds_b))\n",
    "\n",
    "def false_print(): print(\"\"\"\\nThe two x values don't match.\n",
    "Vector x: {} \\n\\nA^(-1)b: \\n{}\"\"\".format(lds_vector_x, verify_x))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "GzrynyTagib-"
   },
   "source": [
    "Note that, finding inverses can be a challenging process if you want to calculate it, but using tensorflow or any other library, you can easily check if the inverse of the matrix exists. If you know the conditions and know how to solve matrix equations using tensorflow, you should be good, but for the reader who wants to go deeper, check [Linear Dependence and Span\n",
    "](https://math.ryerson.ca/~danziger/professor/MTH141/Handouts/depend.pdf) for further examples and definitions."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "rB0seiU_H-hx"
   },
   "source": [
    "# 02.05 - Norms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "p3ieNjy4oHtr"
   },
   "source": [
    "In machine learning if we need to measure the size of vectors, we use a function called a __norm__. And norm is what is generally used to evaluate the error of a model. Formally, the $L^P$ norm is given by:\n",
    "\n",
    "$$\\color{Orange}{||x||_p = \\big(\\displaystyle\\sum_i |x_i|^p\\big)^{1/p}| \\tag{12}}$$\n",
    "\n",
    "for $p \\in \\mathbb{R}, p \\geq 1$\n",
    "\n",
    "On an intuitive level, the norm of a vector $x$ measures the distance from the origin to the point $x$.\n",
    "\n",
    "More rigorously, a norm is any function $f$ that satisfies the following properties:\n",
    "\n",
    "$$\n",
    "\\color{Orange}{f(x) = 0 \\implies x=0} \\\\\n",
    "\\color{Orange}{f(x +y) \\leq f(x) + f(y)} \\\\\n",
    "\\color{Orange}{\\forall \\alpha \\in \\mathbb{R}, f(\\alpha x) = |\\alpha|f(x)  \\tag{13}}\n",
    "$$\n",
    "\n",
    "The $L^2$ norm with *p=2* is known as the __Euclidean norm__. Which is simply the Euclidean distance from the origin to the point identified by $x$. It is also common to measure the size of a vector using the squared $L^2$ norm, which can be calculated simply as $x^{\\top}x$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 51
    },
    "colab_type": "code",
    "id": "1DciOj3Hs2g-",
    "outputId": "4b084d09-1d58-4a6f-a923-ba1389d3a272"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Euclidean Distance: 5.0\n",
      "Vector Size: [ 9. 16.]\n"
     ]
    }
   ],
   "source": [
    "# Euclidean distance between square root(3^2 + 4^2) calculated by setting ord='euclidean'\n",
    "dist_euclidean = tf.norm([3., 4.], ord='euclidean')\n",
    "print(\"Euclidean Distance: {}\".format(dist_euclidean))\n",
    "\n",
    "# Size of the vector [3., 4.]\n",
    "vector_size = tf.multiply(tf.transpose([3., 4.]), [3., 4.])\n",
    "print(\"Vector Size: {}\".format(vector_size))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1_Zg24-vs25u"
   },
   "source": [
    "In many contexts, the squared $L^2$ norm may be undesirable, because it increases very slowly near the origin. In many machine learning applications, it is important to discriminate between elements that are exactly zero and elements that are small but nonzero. In these cases, we turn to a function that grows at the same rate in all locations, but retains mathematical simplicity: the $L^1$ norm, which can be simplified to:\n",
    "\n",
    "$$\\color{Orange}{||x||_1 = \\displaystyle\\sum_i |x_i| \\tag{14}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 281
    },
    "colab_type": "code",
    "id": "OlkUawi5wgBn",
    "outputId": "e622e6db-8fdf-4969-8cdf-5ee412371828"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def SE(x,y,intc,beta):\n",
    "    return (1./len(x))*(0.5)*sum(y - beta * x - intc)**2\n",
    "\n",
    "def L1(intc,beta,lam):\n",
    "    return lam*(tf.abs(intc) + tf.abs(beta))\n",
    "\n",
    "def L2(intc,beta,lam):\n",
    "    return lam*(intc**2 + beta**2)\n",
    "\n",
    "N = 100\n",
    "x = np.random.randn(N)\n",
    "y = 2 * x + np.random.randn(N)\n",
    "\n",
    "beta_N = 100\n",
    "beta = tf.linspace(-100., 100., beta_N)\n",
    "intc = 0.0\n",
    "\n",
    "SE_array = np.array([SE(x,y,intc,i) for i in beta])\n",
    "L1_array = np.array([L1(intc,i,lam=30) for i in beta])\n",
    "L2_array = np.array([L2(intc,i,lam=1) for i in beta])\n",
    "\n",
    "fig1 = plt.figure()\n",
    "ax1 = fig1.add_subplot(1,1,1)\n",
    "ax1.plot(beta, SE_array, label='Squared L2 Norm')\n",
    "ax1.plot(beta, L1_array, label='L1 norm')\n",
    "ax1.plot(beta, L2_array, label='L2 norm')\n",
    "plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'red'})\n",
    "plt.title('The graph of each of the norms', color='w')\n",
    "plt.legend()\n",
    "fig1.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "quBP_kBqwfh8"
   },
   "source": [
    "One other norm that commonly arises in machine learning is the $L^{\\infty}$ norm, also known as the __max norm__. This norm simplifies to the absolute value of the element with the largest magnitude in the vector,\n",
    "\n",
    "$$\\color{Orange}{\\parallel x \\parallel_{\\infty} = max_i |x_i| \\tag{15}}$$\n",
    "\n",
    "If we wish to measure the size of a matrix, in context of deep learning, the most common way to do this is with the __Frobenius norm__:\n",
    "\n",
    "$$\\color{Orange}{\\parallel A \\parallel_F = \\sqrt{\\displaystyle\\sum_{i, j} A^2_{i, j}} \\tag{16}}$$\n",
    "\n",
    "which is analogous to the $L^{2}$ norm of a vector.\n",
    "\n",
    "Meaning for example for a matrix:\n",
    "\n",
    "$$\n",
    "A =\n",
    "\\begin{pmatrix}\n",
    "2 & -1 & 5 \\\\\n",
    "0 & 2 & 1 \\\\\n",
    "3  & 1 & 1  \\\\\n",
    "\\end{pmatrix}\n",
    "$$\n",
    " \n",
    "$||A|| = [2^2 + (-1^2) + 5^2 + 0^2 + 2^2 + 1^2 + 3^2 + 1^2 + 1^2]^{1/2}$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "nfxO_iSN3N5j",
    "outputId": "062c1290-3ba7-423e-f41f-9a4544b582cd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Frobenius norm: 6.78233003616333\n"
     ]
    }
   ],
   "source": [
    "n_matrix_A = tf.constant([[2, -1, 5], [0, 2, 1], [3, 1, 1]], name=\"matrix_A\", dtype=tf.float32)\n",
    "\n",
    "# Frobenius norm for matrix calculated by setting ord='fro'\n",
    "frobenius_norm = tf.norm(n_matrix_A, ord='fro', axis=(0, 1))\n",
    "print(\"Frobenius norm: {}\".format(frobenius_norm))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "B24sBdA63MuZ"
   },
   "source": [
    "\n",
    "The dot product of two vectors can be rewritten in terms of norms as:\n",
    "\n",
    "$$\\color{Orange}{x^{\\top} y = ||x||_2 ||y||_2 cos\\theta \\tag{17}}$$\n",
    "\n",
    "where $\\theta$ is the angle between $x$ and $y$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 337
    },
    "colab_type": "code",
    "id": "Nu_UIRga0esb",
    "outputId": "057a4455-9b4f-4de0-ee84-6a67ce7b96c9"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dot Product can be rewritten in terms of norms, where \n",
      "\n",
      "RHS: 4.0 \n",
      "LHS: [[4.]]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAD8CAYAAACMwORRAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAEiBJREFUeJzt3XmwVOWZgPHn9V5ARFCISFR01JSa\noDMZk1sEdSqVCmYkxgomswiOUTKmcElcIBUHYiUpksxISpNIOYlVjBqSCXEZddwigkssM9SIue4K\nYlyGRTDiFnfWb/64rSK4XPqc7tP93edXZdF9evneLuHh3NOnm0gpIUnKy3ZVDyBJKp9xl6QMGXdJ\nypBxl6QMGXdJypBxl6QMfXDcIy4h4hkiHtps2zAibibij7VfhzZySEnStunNnvscYNwW26YBt5LS\nfsCtteuSpBYRvfoQU8TewA2kdFDt+lLgM6S0mojdgNtJ6YDGjSlJ2haddT5uBCmtrl1+GhjxnveM\nmAxMBuA/B32SXT5a55Kt7dlnnwNgl10+VPEkkrLz/N3Pcmwavi0PqTfub0spEfHeu/8pzQZmA3BT\nV2Jcd+ElW9ENc+YAMGncpErnkJSh38SybX1IvWfL/Kl2OIbar8/U+TySpAaoN+7XASfULp8AXFvO\nOJKkMvTmVMhLgf8FDiBiJREnAjOBzxHxR+Dw2nVJUov44GPuKU18j1vGljuKJKksfkJVkjJk3CUp\nQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8Zd\nkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjJk\n3CUpQ8ZdkjJk3CUpQ8ZdkjJULO4RU4h4mIiHiLiUiO1LmkuSVED9cY/YAzgd6CKlg4AOYEJJc0mS\nCih6WKYTGEhEJ7ADsKr4SJKkouqPe0pPAecBy4HVwJ9JacFW94uYTEQ3Ed2sWVP3cpKk3ityWGYo\nMB7YB9gdGETEcVvdL6XZpNRFSl0MH173cpKk3ityWOZw4ElSWkNK64GrgUPLGUuSVESRuC8HxhCx\nAxEBjAWWlDOWJKmIIsfcFwFXAvcAD9aea3Y5Y0mSiugs9OiUvgd8r5xRJEll8ROqkpQh4y5JGTLu\nkpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh\n4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5JGTLukpQh4y5J\nGTLukpQh4y5JGSoW94idibiSiEeIWELEISXNJUkqoLPg42cBN5HS3xPRH9ihhJkkSQXVH/eInYBP\nA5MASGkdsK6MoSRJxRQ5LLMPsAb4BRH3EnEREYO2ulfEZCK6iehmzZoCy0mSeqtI3DuBTwAXktLB\nwKvAtK3uldJsUuoipS6GDy+wnCSpt4rEfSWwkpQW1a5fSU/sJUkVqz/uKT0NrCDigNqWscDiMoaS\nJBVT9GyZ04C5tTNlngC+WnwkSVJRxeKe0n1AVzmjSJLK4idUJSlDxl2SMmTcJSlDxl2SMmTcJSlD\nxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2S\nMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxl2SMmTcJSlDxr0kg9Oz\nVY+QjUWr4IU3qp5Cam/GvSRdG29gu7Sh6jHa2iPPwXHXw6xuGLp91dNI7a2z8DNEdADdwFOkdFTh\n52tH615mZFrCh9PjVU/Slp56GWbeCZcu6bl+x7HVziPloHjc4QxgCTCkhOdqTyt/Rwcb2TMtrnqS\ntvLiG3B+N8y+D97Y2LPt2FEwapdq55JyUCzuESOBLwD/CkwtY6C2tHw+AHtuWgwpQUTFA7W21zfA\nf9wPP/0D/Hnt29sHdMC0MdXNJeWk6J77+cBZwOD3vEfEZGAyAL/aq+ByLWjTRlhxCwA78gK8sASG\njap4qNa0cRNctgTOuRNWvbL17ZP/Gka+9+8kSdug/jdUI44CniGlu9/3finNJqUuUupi+PC6l2tZ\na+6GN557+/ryBdXN0sLWbYQzb4XTbnn3sO80AM7sav5cUq6KnC1zGPBFIv4PuAz4LBG/LmWqdrJs\n/vtfFwD9O+CCz8G9k+Av3uXdmTO7PENGKlP9cU9pOimNJKW9gQnAbaR0XFmDtY3lW8T8mW54fU01\ns7S4TQnOuwuWvfTO7bvt2HNIRlJ5PM+9iJeWwQuPbLExvXUMXm/blOCMW2Bu7YSi/YbC34zsuTzt\n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\nUpoNzAbY8aauNGncpIYvWYU5c+ZwEouYNGlS1aNIys1vvrrNDymy5/4UsOdm10fWtkmSKlYk7n8A\n9iNiHyL6AxOA68oZS5JURP2HZVLaQMQ3gPlAB3AJKT1c1mCSpPoVO+ae0o3AjeWMIkkqi59QlaQM\nGXdJypBxl6QMGXdJypBxl6QMGXdJypBxl6QMGXdJypBxl6QMGXdJypBxl6QMGXdJypBxl6QMGXdJ\nylCklJq32px4mf4sbd6CTfYiu7Azz1Y9RkPk/NrA19fucn996ziASWnwtjykuXGP6CalruYt2GQ5\nv76cXxv4+tqdr28rHpaRpAwZd0nKULPjPrvJ6zVbzq8v59cGvr525+vbQnOPuUuSmsLDMpKUIeMu\nSRlqftwjziXiESIeIOK/idi56TOULWIcEUuJeIyIaVWPU6qIPYn4HRGLiXiYiDOqHqkhIjqIuJeI\nG6oepXQROxNxZe3P3RIiDql6pNJETKn9vnyIiEuJ2L7qkQqJuISIZ4h4aLNtw4i4mYg/1n4d2pun\nqmLP/WbgIFL6K+BRYHoFM5QnogP4GfB5YBQwkYhR1Q5Vqg3AN0lpFDAG+Hpmr+9NZwBLqh6iQWYB\nN5HSR4GPk8vrjNgDOB3oIqWDgA5gQrVDFTYHGLfFtmnAraS0H3Br7foHan7cU1pAShtq1+4ERjZ9\nhnKNBh4jpSdIaR1wGTC+4pnKk9JqUrqndvllesKwR6UzlS1iJPAF4KKqRyldxE7Ap4GLAUhpHSm9\nWOlM5eoEBhLRCewArKp4nmJSugN4fout44Ff1i7/Eji6N09V9TH3fwbmVTxDUXsAKza7vpLc4vem\niL2Bg4FF1Q5SuvOBs4BNVQ/SAPsAa4Bf1A47XUTEoKqHKkVKTwHnAcuB1cCfSWlBtUM1xAhSWl27\n/DQwojcPakzcI26pHQPb8r/xm93nbHp+5J/bkBlUrogdgauAM0npparHKU3EUcAzpHR31aM0SCfw\nCeBCUjoYeJVe/ljf8nqOPY+n5y+w3YFBRBxX7VAN1nPueq/OX+9s0ACHv+/tEZOAo4CxtP+J9k8B\ne252fWRtWz4i+tET9rmkdHXV45TsMOCLRBwJbA8MIeLXpJRLJFYCK0npzZ+2riSXuMPhwJOktAaA\niKuBQ4FfVzlUA/yJiN1IaTURuwHP9OZBVZwtM46eH4G/SEqvNX398v0B2I+IfYjoT88bOtdVPFN5\nIoKe47VLSOknVY9TupSmk9JIUtqbnv93t2UUdkjpaWAFEQfUtowFFlc4UZmWA2OI2KH2+3QsubxZ\n/E7XASfULp8AXNubBzVmz/39/TswALiZCIA7SenkCuYoR0obiPgGMJ+ed+svIaWHK56qTIcBXwEe\nJOK+2rZvk9KNFc6kbXMaMLe28/EE8NWK5ylHSouIuBK4h55DvPfS7l9DEHEp8BlgFyJWAt8DZgJX\nEHEisAz4x149VfsfFZEkbanqs2UkSQ1g3CUpQ8ZdkjJk3CUpQ8ZdkjJk3CUpQ8ZdkjL0/7ayrddx\n1RdBAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# for x(0, 2) and y(2, 2) cos theta = 45 degrees\n",
    "n_vector_x = tf.constant([[0], [2]], dtype=tf.float32, name=\"vectorX\")\n",
    "n_vector_y = tf.constant([[2], [2]], dtype=tf.float32, name=\"vectorY\")\n",
    "\n",
    "# Due to pi being in, we won't get an exact value so we are rounding our final value\n",
    "prod_RHS = tf.round(tf.multiply(tf.multiply(tf.norm(n_vector_x), tf.norm(n_vector_y)), tf.cos(np.pi/4)))\n",
    "prod_LHS = tf.tensordot(tf.transpose(n_vector_x), n_vector_y, axes=1, name=\"LHS\")\n",
    "\n",
    "predictor = tf.equal(prod_RHS, prod_LHS)\n",
    "def true_print(): print(\"\"\"Dot Product can be rewritten in terms of norms, where \\n\n",
    "RHS: {} \\nLHS: {}\"\"\".format(prod_RHS, prod_LHS))\n",
    "\n",
    "def false_print(): print(\"\"\"Dot Product can not be rewritten in terms of norms, where \\n\n",
    "RHS: {} \\nLHS: {}\"\"\".format(prod_RHS, prod_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)\n",
    "\n",
    "origin=[0,0]\n",
    "plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'red'})\n",
    "plt.xlim(-2, 10)\n",
    "plt.ylim(-1, 10)\n",
    "plt.axvline(x=0, color='grey', zorder=0)\n",
    "plt.axhline(y=0, color='grey', zorder=0)\n",
    "plt.text(-1, 2, r'$\\vec{x}$', size=18)\n",
    "plt.text(2, 1.5, r'$\\vec{y}$', size=18)\n",
    "plt.quiver(*origin, n_vector_x, n_vector_y, color=['#FF9A13','#1190FF'], scale=8)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "9e3oICy6IFT4"
   },
   "source": [
    "# 02.06 - Special Kinds of Matrices and Vectors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "dLyr_66noIdk"
   },
   "source": [
    "__Diagonal__ matrices consist mostly of zeros and have nonzero entries only along the main diagonal. Identity matrix is an example of diagonal matrix. We write $diag(v)$ to denote a square diagonal matrix whose diagonal entries are given by the entries of the vector *v*. \n",
    "\n",
    "To compute $diag(v)x$ we only need to scale each element $x_i$ by $v_i$. In other words:\n",
    "\n",
    "$$\\color{orange}{diag(v)x = v \\odot x \\tag{18}}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 289
    },
    "colab_type": "code",
    "id": "XfoTbabwIGRC",
    "outputId": "bf7f3f99-3f84-4084-dd28-88b4922cf092"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector v: [1 3 8 5 3] \n",
      "Vector x: [9 0 4 1 4]\n",
      "\n",
      "Diagonal of v times x: \n",
      "[[ 9  0  0  0  0]\n",
      " [ 0  0  0  0  0]\n",
      " [ 0  0 32  0  0]\n",
      " [ 0  0  0  5  0]\n",
      " [ 0  0  0  0 12]] \n",
      "\n",
      "is equal to vector v dot vector x: \n",
      "[[ 9  0  0  0  0]\n",
      " [ 0  0  0  0  0]\n",
      " [ 0  0 32  0  0]\n",
      " [ 0  0  0  5  0]\n",
      " [ 0  0  0  0 12]]\n"
     ]
    }
   ],
   "source": [
    "# create vector v and x\n",
    "sp_vector_v = tf.random.uniform([5], minval=0, maxval=10, dtype = tf.int32, seed = 0, name=\"vector_v\")\n",
    "sp_vector_x = tf.random.uniform([5], minval=0, maxval=10, dtype = tf.int32, seed = 0, name=\"vector_x\")\n",
    "print(\"Vector v: {} \\nVector x: {}\\n\".format(sp_vector_v, sp_vector_x))\n",
    "          \n",
    "# RHS diagonal vector v dot diagonal vector x. The linalg.diag converts a vector to a diagonal matrix\n",
    "sp_RHS = tf.tensordot(tf.linalg.diag(sp_vector_v), tf.linalg.diag(sp_vector_x), axes=1)\n",
    "\n",
    "# LHS diag(v)x\n",
    "sp_LHS = tf.multiply(tf.linalg.diag(sp_vector_v), sp_vector_x)\n",
    "    \n",
    "predictor = tf.reduce_all(tf.equal(sp_RHS, sp_LHS))\n",
    "def true_print(): print(\"Diagonal of v times x: \\n{} \\n\\nis equal to vector v dot vector x: \\n{}\".format(sp_RHS, sp_LHS))\n",
    "def false_print(): print(\"Diagonal of v times x: \\n{} \\n\\nis NOT equal to vector v dot vector x: \\n{}\".format(sp_RHS, sp_LHS))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "k56YwhRu6__p"
   },
   "source": [
    "Inverting a square diagonal matrix is also efficient. The inverse exists only if every diagonal entry is nonzero, and in that case:\n",
    "\n",
    "$$\\color{orange}{diag(v)^{-1} = diag([1/v_1, \\cdots , 1/v_n]^{\\top}) \\tag{19}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 255
    },
    "colab_type": "code",
    "id": "Vyx5Iqzy7Aat",
    "outputId": "7f94f239-7d64-4f1c-aba5-d79d474140a1"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector v: [1.9077636 9.731502  8.638878  1.4345318 1.4367076]\n",
      "The inverse of LHS: \n",
      "[[0.524174   0.         0.         0.         0.        ]\n",
      " [0.         0.10275906 0.         0.         0.        ]\n",
      " [0.         0.         0.11575577 0.         0.        ]\n",
      " [0.         0.         0.         0.6970915  0.        ]\n",
      " [0.         0.         0.         0.         0.69603586]] \n",
      "\n",
      "Match the inverse of RHS: \n",
      "[[0.524174   0.         0.         0.         0.        ]\n",
      " [0.         0.10275906 0.         0.         0.        ]\n",
      " [0.         0.         0.11575577 0.         0.        ]\n",
      " [0.         0.         0.         0.6970915  0.        ]\n",
      " [0.         0.         0.         0.         0.69603586]]\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    # try creating a vector_v with zero elements and see what happens\n",
    "    d_vector_v = tf.random.uniform([5], minval=1, maxval=10, dtype = tf.float32, seed = 0, name=\"vector_v\")\n",
    "    print(\"Vector v: {}\".format(d_vector_v))\n",
    "    \n",
    "    # linalg.diag converts a vector to a diagonal matrix\n",
    "    diag_RHS = tf.linalg.diag(tf.transpose(1. / d_vector_v))\n",
    "    \n",
    "    # we convert the vector to diagonal matrix and take it's inverse\n",
    "    inv_LHS = tf.linalg.inv(tf.linalg.diag(d_vector_v))\n",
    "\n",
    "    predictor = tf.reduce_all(tf.equal(diag_RHS, inv_LHS))\n",
    "    def true_print(): print(\"The inverse of LHS: \\n{} \\n\\nMatch the inverse of RHS: \\n{}\".format(diag_RHS, inv_LHS))\n",
    "    def false_print(): print(\"The inverse of LHS: \\n{} \\n\\n Does not match the inverse of RHS: \\n{}\".format(diag_RHS, inv_LHS))\n",
    "    tf.cond(predictor, true_print, false_print)\n",
    "    \n",
    "except:\n",
    "    print(\"The inverse exists only if every diagonal is nonzero, your vector looks: \\n{}\".format(d_vector_v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Y9Q4K-yc7Ayz"
   },
   "source": [
    "Not all diagonal matrices need be square. It is possible to construct a rectangular diagonal matrix. Nonsquare diagonal matrices do not have inverses, but we can still multiply by them cheaply. For a nonsquare diagonal matrix $D$, the product $Dx$ will involve scaling each element of $x$ and either concatenating some zeros to the result, if $D$ is taller than it is wide, or discarding some of the last elements of the vector, if $D$ is wider than it is tall."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "hb5WlSwb_Pqh"
   },
   "source": [
    "A __symmetric__ matrix is any matrix that is equal to its own transpose:\n",
    "\n",
    "$A = A^{\\top}$\n",
    "\n",
    "Symmetric matrices often arise when the entries are generated by some function of two arguments that does not depend on the order of the arguments. For example, if $A$ is a matrix of distance measurements, with $A_{i,j}$ giving the distance from point *i* to point *j*, then $A_{i, j} = A_{j, i}$ because distance functions are symmetric."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 170
    },
    "colab_type": "code",
    "id": "YjaQdH1V_QEk",
    "outputId": "ed1503c7-3366-46ad-fecf-d65b68154cc4"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[0 1 3]\n",
      " [1 2 4]\n",
      " [3 4 5]] \n",
      "\n",
      "Matches the the transpose of Matrix A: \n",
      "[[0 1 3]\n",
      " [1 2 4]\n",
      " [3 4 5]]\n"
     ]
    }
   ],
   "source": [
    "# create a symmetric matrix\n",
    "sp_matrix_A = tf.constant([[0, 1, 3], [1, 2, 4], [3, 4, 5]], name=\"matrix_a\", dtype=tf.int32)\n",
    "\n",
    "# get the transpose of matrix A\n",
    "sp_transpose_a = tf.transpose(sp_matrix_A)\n",
    "\n",
    "predictor = tf.reduce_all(tf.equal(sp_matrix_A, sp_transpose_a))\n",
    "def true_print(): print(\"Matrix A: \\n{} \\n\\nMatches the the transpose of Matrix A: \\n{}\".format(sp_matrix_A, sp_transpose_a))\n",
    "def false_print(): print(\"Matrix A: \\n{} \\n\\nDoes Not match the the transpose of Matrix A: \\n{}\".format(sp_matrix_A, sp_transpose_a))\n",
    "\n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "zLbGcoSU_QsK"
   },
   "source": [
    "A vector $x$ and a vector $y$ are __*orthogonal*__ to each other if $x^{\\top} y = 0$. If both vectors have nonzero norm, this means that they are at a 90 degree angle to each other."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 422
    },
    "colab_type": "code",
    "id": "nTJgnoJS_RH0",
    "outputId": "5dcd8536-33ca-4ee4-c718-b84e73a27db2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector x: [2. 2.] \n",
      "Vector y: [ 2. -2.]\n",
      "\n",
      "X transpose times y = 0.0\n",
      "\n",
      "Norm x: 2.8284270763397217 \n",
      "Norm y: 2.8284270763397217\n",
      "\n",
      "Angle between vector x and vector y is: 90.0 degrees\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Lets create two vectors\n",
    "ortho_vector_x = tf.constant([2, 2], dtype=tf.float32, name=\"vector_x\")\n",
    "ortho_vector_y = tf.constant([2, -2], dtype=tf.float32, name=\"vector_y\")\n",
    "print(\"Vector x: {} \\nVector y: {}\\n\".format(ortho_vector_x, ortho_vector_y))\n",
    "\n",
    "# lets verify if x transpose dot y is zero\n",
    "ortho_LHS = tf.tensordot(tf.transpose(ortho_vector_x), ortho_vector_y, axes=1)\n",
    "print(\"X transpose times y = {}\\n\".format(ortho_LHS))\n",
    "\n",
    "# let's see what their norms are\n",
    "ortho_norm_x = tf.norm(ortho_vector_x)\n",
    "ortho_norm_y = tf.norm(ortho_vector_y)\n",
    "print(\"Norm x: {} \\nNorm y: {}\\n\".format(ortho_norm_x, ortho_norm_y))\n",
    "\n",
    "# If they have non zero norm, let's see what angle they are to each other\n",
    "if tf.logical_and(ortho_norm_x > 0, ortho_norm_y > 0):\n",
    "    # from the equation cos theta = (x dot y)/(norm of x times norm y)\n",
    "    cosine_angle = (tf.divide(tf.tensordot(ortho_vector_x, ortho_vector_y, axes=1), tf.multiply(ortho_norm_x, ortho_norm_y)))\n",
    "    print(\"Angle between vector x and vector y is: {} degrees\".format(tf.acos(cosine_angle) * 180 /np.pi))\n",
    "\n",
    "    origin=[0,0]\n",
    "    plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'red'})\n",
    "    plt.xlim(-1, 10)\n",
    "    plt.ylim(-10, 10)\n",
    "    plt.axvline(x=0, color='grey', zorder=0)\n",
    "    plt.axhline(y=0, color='grey', zorder=0)\n",
    "    plt.text(1, 4, r'$\\vec{x}$', size=18)\n",
    "    plt.text(1, -6, r'$\\vec{y}$', size=18)\n",
    "    plt.quiver(*origin, ortho_vector_x, ortho_vector_y, color=['#FF9A13','#1190FF'], scale=8)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "S9TQJmFrvjCs"
   },
   "source": [
    "A __unit vector__ is a vector with a __unit norm__: $||x||_2 = 1$.\n",
    "\n",
    "If two vectors are not only are orthogonal but also have unit norm, we call them __orthonormal__."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Dm3RXJi_CB1e"
   },
   "source": [
    "A __orthogonal matrix__ is a square matrix whose rows are mutually orthonormal and whose columns are mutually orthonormal:\n",
    "\n",
    "$$\\color{Orange}{A^{\\top} A = AA^{\\top} = I \\tag{20}}$$\n",
    "\n",
    "which implies $A^{-1} = A^{\\top}$\n",
    "\n",
    "so orthogonal matrices are of interest because their inverse is very cheap to compute."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 524
    },
    "colab_type": "code",
    "id": "Q2JOiskHCCq8",
    "outputId": "123206f6-252e-4c80-fb3c-0c1f7c9f2f99"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "<tf.Variable 'matrixA:0' shape=(2, 2) dtype=float32, numpy=\n",
      "array([[ 0.87758255, -0.47942555],\n",
      "       [ 0.47942555,  0.87758255]], dtype=float32)>\n",
      "\n",
      "Columns are orthogonal: 0.0\n",
      "Rows are orthogonal: 0.0\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzt3XlcVWX+wPHPI8giCqLYImgu5K65\na2qmlmVmmi1mTRaVOdk41qRWLlNaaVna/HQaM7O0Pa20lDSV0Jxm0kShxCW3UEnHBREUFFme3x/P\nZRMQ8B7uuVy+79frvjw859xzvvegX899znO+j9JaI4QQwnNVszsAIYQQFUsSvRBCeDhJ9EII4eEk\n0QshhIeTRC+EEB5OEr0QQng46xK9Ul4oFYtSkZbtUwghhNOsvKJ/Cthl4f6EEEJYwJpEr1QYcDuw\n0JL9CSGEsIy3Rfv5P+BZoFaJWyg1ChgFwEcBnQhpYdGhhbDGyZNJAISE1LU5EiFKcGrrSR7Q9cr7\nNucTvVKDgONovRWl+pS4ndYLgAUAfNdZMyDG6UMLYaXIxYsBiBgQYWscQpToU3Xwct5mRddNT2Aw\nSiUAnwP9UOpjC/YrhBDCAs4neq0nonUYWjcChgPRaP2g0/sVQghhCRlHL4QQHs6qm7GG1huADZbu\nUwghhFPkil4IITycJHohhPBwkuiFEMLDSaIXQggPJ4leCCE8nCR6IYTwcJLohRDCw0miF0IIDyeJ\nXgghPJwkeiGE8HCS6IUQwsNJohdCCA8niV4IITycJHohhPBwkuiFEMLDSaIXQggPJ4leCCE8nCR6\nIYTwcJLohRDCw0miF0IIDyeJXlye8+chK8vuKERZZJ61OwJhM0n04vJ4ecGjj0Jmpt2RiEs5cxi2\nvm53FMJmzid6pfxQ6meU+gWldqDUNAviEu6uenXYvx/uuw8uXLA7GlGc1IMQORj86todibCZFVf0\nGUA/tL4OaA8MQKnuFuxXuLuuXWH5chg2TJK9u0lNgG+HwNnDcEVHu6MRNnM+0Wut0Tq3E7C646Wd\n3q9wf926mT+/+QbuvhsyMuyNRxgpB8yV/NlEQEFIe7sjEjazpo9eKS+UigOOA+vQenMx24xCqRiU\niuHECUsOK2zWtWv+cmQk3HWXuUkr7JOy31zJpx0xP9duBj617I1J2M6aRK91Nlq3B8KArijVppht\nFqB1Z7TuTL16lhxW2KxxY6hboP931SoYOlSSvV1O7zVX8mlH89uk20Zg9agbrU8D64EBlu5XuCel\nCl/VA3z3HQweDOfO2RNTVZW8ByKHQPqxwu31OtgTj3ArVoy6qYdStR3L/kB/YLfT+xWVw8WJHmDd\nOrjjDkhPd308VdGp3aa75tzxouuu6OT6eITbseKK/mpgPUr9CmzB9NFHWrBfURkUl+irVYOff4bH\nHpOHqira2UT4/jHITCu6zssX6rRyfUzC7Xg7vQetfwXk+2FV1aVL0bb774ePP3Z9LFVRzTC49z/m\n6dclXQtf1ddtB9Wq2xebcBvyZKxwTr165qZsWBjcdptp++QT2LbN3riqmu1v5yf5FiPMn3IjVjg4\nf0UvRPfuMHKkSfZr10J2NkyYAFFR5oatqFjpx+GXt8xyndbQcxZ4B0A9GT8vDLmiF86bORP69YNm\nzeDPfzZt0dGwZo29cVUV296ALEcffdcXoZoXdJkCoX1sDUu4D0n0wnkNGuQvv/AC1Kxplp991lzd\ni4pzei/s/tAsh94IYX3Nsrcf+IfYF5dwK5LohbWuvNIkeIDt2+WmbEXbMh204z/Tri9KV5koliR6\nYb1nnoGrrjLLU6bIw1MOWmuWLl1KamqqNTs89jMkOEYyh98LIe2s2a/wOJLohfUCAuCll8xyYiLM\nnWtvPG5CKUVaWhrDhw8n29kuLa1h81SzXM0HOk9yOj7huSTRi4rxyCPQsqVZnjEDTp60Nx4Xe/vt\nt2nUqFGR17Rp01i9ejUrV6507gAJq8wVPUCbx6FWg0tvL6o0SfSiXMrc/eDtDa+9ZpZTU2H69IoP\nzo2MHj2ahISEIq+RI0cSERHBnXfeefk7z8mELY5vTD5BcN3T1gQtPJYkelEu5ep+uOMOuOEGs/yv\nf8GBAxUfoBvTWtOvXz8WLFjg3I52f2TKEQN0+Bv4BTsfnPBo8sCUKNHbb7/NzJkzi1138OBBVq5c\neekrU6XgjTfMA1WZmTB5Mnz2WQVF6/6UUvTo0cO5nVw4Y8bNgyl/0Gqk84EJjydX9KJElnQ/dOsG\n995rlj//HLZsqdig3Vznzp1p27bt5e9g+zw455i4p/MkM15eiFJIohflclndDzNmmD57MGPsddWc\naTIrK4v4+Hg6dLjMGoDp/4Nf55nlum0h/B7rghMeTRK9KJfc7ofq1ctRFTE8HEaPNssbNsDq1RUS\nm7vbuXMnGRkZl5/otxYoddBtKij55yvKRv6mVAE5OTlcuHDB3iD+/neo5Zi7tIqWRoiLiwO4vESf\nvAd+czxlHNrXlDsQoowk0XuY5557DqUUu3fvZuzYsYSGhuLt7c3WrVsB2Lt3LxEREYSGhuLj40N4\neDizZ89GF+hOmTNnDkop9uzZk9eWkpJCYGAgSikSEhLy2hMTE6levTovvPDCpQOrVw+ee84s79gB\nH3xg2WeuSCdPnmTSpEm0bt0af39/6tSpQ+/evfnmm2/ytinLOQWIjY0FoH37y6gqueVlR6kDBd1K\nOddCXERG3XiY2NhY/P39GTx4MM2aNWPixImkpqbSpk0b1q5dy9ChQwkNDWXMmDEEBwcTGRnJ+PHj\nSUpKYsaMGQAEB5vhemfOnMnb7/z58znvmPQ7OTmZRo0aATBv3jy8vLwYM2ZM6cH97W8wbx4cOWKu\n8IcPhxo1rD0BFoqPj6d///6kpKTw+OOP065dO44dO8bq1avZtWsXQ4YMKfM5BXNF37hxY2rXrl2+\nQI7+BAcd3V3XDjP980KUh9ba9a/VnbSoGCEhIRrQM2fOLNR+4MABHRAQoHv16qXT0tIKrevWrZv2\n9fXNa1+xYoUGdHR0tNZa64yMDF2/fn09atQoDeioqCittdbp6em6bt26euTIkWUPcOFCrc3tWK2n\nT3fik1pv0aJFetGiRVprrZOTk3VYWJhu2LCh3rdvX5Ftz507V65zqrXWtWvX1kOHDi1fUDk5Wn99\nq9YL6mr9Xn2tzxwu9+cSHuQTYvRl5FzpuvEgiYmJnDx5kp49e/JsbgVJh1deeYX09HQWLlxIjYuu\novv06UNGRgYHDx4Eil7Rf/LJJyQlJTF58mTAXNHntp86dYpnnnmm7EE+/DC0bm2WX3sNTpwo9+d0\nhVdffZXExEQ+/fRTmjZtWmS9n59fuc5pQkICp0+fLn//fEIkHI8xy61HmbHzQpSTJHoPktsH/NBD\nDxVqz8nJYfny5fTt25fmzZsXeZ929CUHBAQA+Yk+NTUVrTWzZs3iwQcfpGHDhvj6+uYl+rlz53Lb\nbbfRMremDWYI4VNPPUWdOnWoXbs2jz32WF6XD2CGWeY+hHXmDLzyijUf3kJaaz7++GO6d+9Oz549\ni92mvOc093dTMNGXeq5yMuFnR6kD32BoL6UOxOWRRO9Btjnmae3Xr1+h9sTERJKTk2nVqlWx74uP\njyc4OJgGjglE6tSpA5gr+lWrVrFr1y7GjRsHQFBQEKdOnSI6Oprt27fnteeaMWMG69evZ/v27ezd\nu5edO3cW+XbBwIHQp49ZnjcP9u1z5mNb7vjx4xw5coTu3buXuE15z2lxib7Uc7XrQ0j93Sx3eAZ8\ng5z8ZKKqkkTvQWJjYwkKCirS1aAck1H4+PgUec/hw4dZt24dQ4cOzduuYNfNrFmzuP322/Ou2gMD\nA0lOTmbOnDl06NChyH8qCxcuZNKkSYSGhlKvXj2mTp3K4sWLC9fFUQpef90sZ2WZ0ghuJCUlBcg/\nb8Up7zmNi4ujXr16hIaG5m13yXNVqNRBQ2j1qFUfT1RBzid6pRqg1HqU2olSO1DqKQviEpchNjaW\njh07FklQYWFhBAYGsnHjxkLt586dY8SIEXh5eTFx4sS8dj8/P/z8/IiOjmbDhg1MmDAhb11QUBBb\nt24lMjKyyNX86dOnOXz4cKHhgx07duTMmTOFhmQC0KWLGXUDsHQpbN7sxCe3VlhYGD4+PkRFRRUp\n3Ka1Jisrq9znNDY2ttDVfKnn6te34LyjtHOXSeDla/0HFVWGFVf0WcA4tG4FdAf+glLFf58VFebU\nqVMcOnSITp06FVmnlGLKlCnExMQwaNAg5s+fz8yZM2nfvj2bNm1iyZIlhIeHF3pPcHAwa9asoWvX\nrvTu3TuvPTAwkKioKOrXr8+wYcMKvSf35m3B4YO5ywWHauaZPh1yn7B1o9IINWrUYPTo0Wzfvp1e\nvXrx5ptv8s477zBu3DiaNGlCenp6uc5pUlISiYmJhRL9Jc/V8QP5pQ5C2kHTu1z0yYWncn4cvdZH\ngaOO5TMotQsIBXY6vW9RZrl9wB07dix2/fjx4wEzHn7t2rXUrVuXvn37smzZMlrnjoIpIDg4mKNH\njxa5ag8KMv3EY8eOLVIGoZbjydeUlBSuckwlePr06ULrCmnSBJ58EubMgY0bITLSlDZ2A7Nnz6ZF\nixYsWLCAqVOnAtC4cWMefvhhAgMDgbKf0+IelLrkuUr4BLId0y92nSqlDoTzLmdMZokvaKThkIbA\nYtaN0hCjIUZ/2LDChpkKezVo0EB//vnneT+vWbNG16pVS2dlZRX/hhMntA4MNOPqW7bUOjPTRZEW\nVXAcvSsUe65qBuisd0LMuPnV97ksFlFJ2D6OXqmawFfA02hddPohrRegdWe07ky9epYdVriXkSNH\n8uqrr3LkyBFOnDjB1KlTiYiIwMvLq/g3hIRAbl/2rl2waJHrgrVZseeqXyheSgMKukqpA2ENaxK9\nUtUxSf4TtF5myT5FpTRp0iR69+5N69atCQ8Pp2XLliVOXpLnqacgzPEg0IsvQlpaxQfqBoqcq2vq\nMPMWxw3YZvdDHbnVJaxhxagbBbwH7ELrN53en6jUvL29mTt3LsnJyaSkpPDee+/h7+9/6Tf5+8PL\nL5vlo0fhH/+o+EDdQKFzdfo0792Xgb+PAi8/6PS83eEJD2LFFX1PYATQD6XiHK+BFuxXVCUjRkDu\nzEszZ8Lx4/bG42oHvoET5qYtbf4MNevbG4/wKM4neq1/RGuF1u3Qur3jtcqC2ERV4uWVXxrh7Fl4\n6SV743Gl7AuwxVEKwrcOtJdHUYS1ZNyWcB8DBkDuk7bvvAMF6uF7tF2L4UyCWe44DnwC7YxGeCBJ\n9MJ9XFwaYdIke+NxhQupEDvbLNdqBC0fsTUc4Zkk0Qv30qkTPPCAWf7qK/jpJ3vjqWi//BPOJ5nl\nLpPBq2jtHCGcJYleuJ9XXoHcYmFuVBrBcmePwPa3zXJIe2gyxN547PS//8GKFTBlCowbB+fO2R2R\nR5GpBIX7adwYxoyBN9+EH380CWCIBybBrTMh21F/vtuLVafUwdmzsHUr/Pxz/uvQIbOuVStYv94M\nuRWWkUQv3NPkyfD++3D6tJlU/PbbzaQlnuLUTtj7mVlu0B/q32BvPBXpjz9MHaPcpL5zJ+TkFN2u\nZUuIjoYrrnB9jB6uilxCiEqnTp380gi//QbvvWdvPFb7+SXQOeYq3tNLHYSEwP79prxFfHzxSb55\nc5Pkr7zS9fFVAZLohfv661/BMUMTL75ovvJ7giP/hsNRZrnZ/VCn5aW3r+x8fc19l/vvL379tdea\nJO+o4imsJ4leuC9///w5ZY8dg9mz7Y3HCjoHNk8zy17+nl/qICcHliwx3TKfflp0fXi46ZOvL08C\nVyRJ9MK9/elPcN11ZvmNN8zojMrswNdwMs4stx0NAVfbG09F2rABunUzM4kdOGDaGjXKX9+kiUny\nBaZXFBVDEr1wb15e+Q9RpaXBtGn2xuOM7AzYMt0s+9WF6/5qbzwVZccOGDQI+vaFmBjTFhICc+fC\n7t3m58aNTZLPrVoqKpQkeuH+brkF+vc3y+++a27OVkY7F8GZg2a5w3jwKWbWrcrsjz9g5Eho1w6+\n/da0+fmZJ5z37TP3XHx9TTfO+vXQsKG98VYhkuhF5TBzpimRkJ2dPxqnMslIyS91ENgYWj5sbzxW\nSk01Dzpde60ZHZWTA9WqwaOPwt69Zm5gxxSUAHz/PVxzjX3xVkGS6EXl0KGD6a8HWL4c/vMfe+Mp\nr1/mQEayWe4yxTNKHVy4AP/8JzRtapJ57tOsAwdCXJxJ+sV1zVztwfcl3JQkelF5FCyNMGFC5SmN\ncPYPiF9glut1hMaD7Y3HWVrDF1+Yp1jHjoWTjlmxOnUywyS//TZ/bgHhFiTRi8rjmmtMYgFT7Gz5\ncnvjKautrxUodTDVdEFVVhs3QvfuMGyYeQgKzI3Vzz4zT7327WtvfKJYkuhF5TJpEgQHm+Xnn4fM\nTHvjKU3SDtjzuVlueCtc3cPeeC7Xrl2m3tCNN5qEDubp5X/8w6wbPtz0ywu3JL8ZUbkEB5s6OGBu\n9L37rr3xlObnlwBdeUsdHD0Ko0ZBmzamuByYkTTPPWeu6J9+2oykEW5NEr2ofP7yl/xRG9OmwZkz\n9sZTkj9+gMTvzXLzP0Fwc3vjKY8zZ+CFF8yTq+++a0bSKAUREWbmr9deg9q17Y5SlJEkelH5+PmZ\nUR5gJhGfNcveeIqjc2DzVLPsXQM6PmtrOGWWmQn/+pcZSfPyy5CebtoHDDAjaRYtyq8/JCoNSfSi\ncrr/fjPkEkyiP3rU3ngutv8rSNpulitDqQOtzYxerVubuQBOnDDtHTrAunWwerV5EEpUSpLoReVU\nrVp+aYT0dJg61dZwCsnOgC0zzLJfCLQbY288pfnxR+jZE+65x9z3ANM19vHHpoTBzTfbG59wmjWJ\nXqn3Ueo4SsVbsj8hyuLmm+HWW83ywoVm9Ic72PEenD1sljs9676lDnbvhqFD4YYb8ufmDQ42VUJ3\n7zYPqMlIGo9g1W9xMTDAon0JUXa5pRFycsxwS7tlnIa4N81yYBNoMcLeeIrzv//B6NFmJM3XX5s2\nX1/zENr+/fDMM+Y+iPAY1iR6rTcCpyzZlxDlcd118NBDZnnFCvNAj53i/s8ke4Cuf4dq1e2Np6Cz\nZ00XV3g4zJ9v6gYpBSNGmEJxr7+e/4xCJaS1ZunSpaSmptodituR72Wi8nv55fyx3M8+a19phDOH\nYYdjXP8VnaHRIHviuFhmpkns4eFmOGpammnv3x+2bYMPP/SIImNKKdLS0hg+fDjZ2dl2h+NWXJfo\nlRqFUjEoFZN3R18IKzRoYB7cAdi82YwesUPMq+ZGLLhHqQOtTddM27amq+bYMdN+3XWwZg2sXQvt\n29sb42V6++23adSoUZHXtGnTWL16NStXrrQ7RLfiukSv9QK07ozWnalXz2WHFVXE88+bR/LBlDG+\ncMG1x0/aDvu+MMvXDISrurv2+Bf76Sdzk3Xo0Pz6/Q0amKv3bdtMjf9KbPTo0SQkJBR5jRw5koiI\nCO688067Q3Qr0nUjPEPt2qYmOphJLhYscO3xN+eWOvAyffN22bMH7r4bevTIL+UcFGT63/fsMf3x\nHjqSRmtNv379WODq330lYNXwys+An4DmKJWIUo9Zsl8hyuPJJ/PnJJ02zUyI4QqJ6+GP9Wa5xQio\nfa1rjlvQsWOmNESrVrBsmWnz8TEjaPbvNyNqPHwkjVKKHj16UL26G90AdxNWjbq5H62vRuvqaB2G\n1u9Zsl8hysPXF2Y4HlQ6eTL/gaqKpHNgs2MeW+8A6Dih4o9ZUFqauRkdHg7z5pmRNGDGwP/2mxkT\nX7eua2Mqh5ycHC64uputCvLM73Ci6rrvPjMBBsCbb5p5TCvSvi/hlOM5wXZPQo0rK/Z4ubKyTPdU\neLgpPnb2rGnv1888zfrxx/nfbtzEc889h1KK3bt3M3bsWEJDQ/H29mbr1q0A7N27l4iICEJDQ/Hx\n8SE8PJzZs2ejC4yimjNnDkop9uzZk9eWkpJCYGAgSikSEhLy2hMTE6levTovvFAJq4ZazNvuAISw\nVLVq8MYbJuGdOwcvvmiemq0IWedhi6O4mv8V0PbJijlOQVrDypWmTPDu3fntbduabzC33mr/aJ8S\nxMbG4u/vz+DBg2nWrBkTJ04kNTWVNm3asHbtWoYOHUpoaChjxowhODiYyMhIxo8fT1JSEjMc39SC\nHeP8zxSoWDp//nzOnzcTuyQnJ9PI8R/cvHnz8PLyYswYNy9B4Qpaa9e/VnfSQlSogQO1Bq2rVdM6\nPr5Mb1m0aJFetGhR2Y8RN1frBXXNa8f7lxdneWzapPUNN5jPlfsKC9N60SKts7Iq/vhOCgkJ0YCe\nOXNmofYDBw7ogIAA3atXL52WllZoXbdu3bSvr29e+4oVKzSgo6OjtdZaZ2Rk6Pr16+tRo0ZpQEdF\nRWmttU5PT9d169bVI0eOdMEnc6FPiNGXkXOl60Z4ppkzzdV9RZVGOJ9snoIFCAqHFg9af4xc+/aZ\nqfu6d4d//9u0BQaamvB79pga8V5eFXd8CyQmJnLy5El69uzJs88WLtn8yiuvkJ6ezsKFC6lRo0ah\ndX369CEjI4ODBw8CRa/oP/nkE5KSkpjsmIwmOTk5r/3UqVM888wzFfq5KgtJ9MIztWkDDz9sliMj\nYcMGa/cf9w+4kGKWu75QMaUOTpyAv/4VWrY0k3EDVK9uHg7bv9903/j7W3/cChAbGwvAQ7nlKhxy\ncnJYvnw5ffv2pXnzohOzaEf/fEBAAJCf6FNTU9FaM2vWLB588EEaNmyIr69vXqKfO3cut912Gy1b\ntszb19KlS+nVqxc1a9bM696pKiTRC8/10kv5QwonTDBX91Y4cwh2OPr9r+wK19xmzX5zpaebiVWa\nNoW33jI3XsHMy7p7t5mnNSTE2mNWsG3btgHQr1+/Qu2JiYkkJyfTqlWrYt8XHx9PcHAwDRyTndRx\nPBR35swZVq1axa5duxg3bhwAQUFBnDp1iujoaLZv357Xnis4OJgxY8YwPXfSmipEEr3wXGFh8Le/\nmeWYmPyrYmfFzIAcx5BAK0sdZGfDe+/Btdeah79ybzj26WMm5P7sM2jSxJpjuVhsbCxBQUE0bdq0\nULtynDsfH58i7zl8+DDr1q1j6NChedsV7LqZNWsWt99+e95Ve2BgIMnJycyZM4cOHToU+U+lf//+\nDB8+nGs8oK5PeUmiF57tuefyx5FPnAgZGc7t7+QvZkglmKJlV3Z1bn9gbqt++62pQTNyJBw5Ytpb\ntzbt0dHQpYvzx7FRbGwsHTt2zEvYucLCwggMDGTjRVVHz507x4gRI/Dy8mLixIl57X5+fvj5+REd\nHc2GDRuYMCH/uYWgoCC2bt1KZGRkkav5qk4SvfBsQUFmnDnA77+bKo6XS+v8eWCVF3SZ4nR4bNkC\nffvCoEGwY4dpq1/fXNn/8gsMHOi2wyXL6tSpUxw6dIhOuc83FKCUYsqUKcTExDBo0CDmz5/PzJkz\nad++PZs2bWLJkiWEh4cXek9wcDBr1qyha9eu9O7dO689MDCQqKgo6tevz7Bhwyr8c1UmMo5eeL4n\nnoA5c+DAAfMUaUSE+Q+gvBKj4Yhj1EvLh6F2+KW3v5T9+2HyZFiyJL+tVi0zQujpp+Gi0SeVWe6N\n2I4dOxa7fvz48YAZD7927Vrq1q1L3759WbZsGa1bty6yfXBwMEePHi1y1R7k+J2OHTtWyiBcRBK9\n8Hw+PvDqq+ap2aQkM/Qyt1RCWeVkw8+OUgfVA6DD+MuL5eRJeOUVU64gM9O0eXubMsJ//zueWNn1\npptuKvR068WUUkyYMKFQN8yl7Mj95nOR5cuXX1Z8VYF03Yiq4d578/u5//EPSEws3/v3fQGndprl\ndn+FGleU7/3nzplx702bmm8XuUl+2DAz1+3cuR6Z5N1JdnY258+fJzMzE60158+fJ8PZezaVhCR6\nUTUolV/k7Pz5/H77ssg6Z0bagKPUweiyvzc7GxYtgmbNzM3g3IqaN9wAmzaZrptwJ7qARJl99NFH\n+Pv7M2zYMA4dOoS/v3+xY/c9kSR6UXX06WNuegIsXgzbt5ftffHvQppjJEyn50zXTWm0htWrzQxO\njz6a/w2iZUszt+0PP0C3buX9BMIJERERRUoDFCyC5skk0Yuq5bXXTGkErc3Qy9KcTzJPwYKpM9/8\nT6W/Z+tWuPlmM2Im3lHZ8qqrTLXJX3+FO+6o9CNpROUiiV5ULa1bmytsMFfc339/6e1j34RMx4NL\nXV+AapcYv/D776YOfOfOZuw7QM2a5gndffvg8cfNjVchXEwSvah6pk3LrxHz7LMll0ZITYCd75vl\nq7pDwwHFb5eUZGZyatECPv3UtHl7mxmf9u83o2kCytDdI0QFkUQvqp769SF3DPa2bYXHshcUMx1y\nHKNjuk0r2t1y7py5wdu0qRnJkztT0t13m4ef3noLrijn6BwhKoAkelE1TZiQXxhs0qSipRFOxMJ+\nx7jsxkPgigJPdWZnwwcfQPPmpp8/xVHFsmdP+O9/4csvzSgbIdyEJHpRNQUGmtmnABISzANMuQqV\nOvCGLpPz161ZAx07mqdrDx82bc2bw9dfm1rx11/vguCFKB9J9KLqGjUqfwz7yy/jk5Zmlg9HwdH/\nmOWWERDUBGJjoX9/GDDAjJwBuPJKUzsnPh6GDJGRNMJtSaIXVVduaQSA5GTafvstSucUKHVQE0KG\nwYgR5io+Ksq0BwTA1KlmJM2f/ywjaYTbsybRKzUApX5DqX0oVQHztglRQe6+O+/BpVbr1tHq5HpI\n3g1pORDdGNpdDx9/bLb18jIF0vbtM90+NWvaGLgQZef8pYhSXsC/gP5AIrAFpVag9U6n9y1ERVMK\n3ngDevfGKyuL9l8thwYaVp2H9A352915p7n6b9HCtlCFuFxWXNF3Bfah9QG0vgB8DgyxYL9CuMYN\nN5g+dqD65jT4Mh3SHWPrr78efvwRli+XJC8qLSs6F0OBwwV+TgQuWcTj5MkkIhcvtuDQQlgjqGtX\nbl+9Cq9AjdfJLC5c4U/MvcPY0+lG2LvXvISwWUTRGRfLxHU3Y5UahVIxKBXjlzsXphBuIqV+fd6b\n9DQ5TwbCAwH4TPWn23Wr6ZL5xp9+AAASHUlEQVSzEl+dZnd4QjhFXWpCgLLtQV0PTEXrWx0/mwke\ntX61xPd811kzIMa54wphscWLFxOoT3BXg53w+8r8FT6BcN3T0OZx8Pa3L0AhPlVbeUB3Lu/brLii\n3wJci1KNUcoHGA6ssGC/QrhcqqoHNy+CwavhSkcP5IVU2PISLO0Gez43s00JUYk4n+i1zgLGAGuA\nXcBStC5+ri8hKosru8AdkdD/QwhyPFSVdgR+GAPL+8HhaPMErRCVgDV99FqvQutmaN0Uradbsk8h\n7KYUNBoI9/wIvWaZ2aUATu2A74bB6nvg5K/2xihEGciTsUKUppq3KYVw389mhilvR8nhP34wV/fr\nR8OZw5fchRB2kkQvRFlVrwkdJ5iE3/IRUF6mfd8Xpv9+0wtwPtneGIUohiR6IcqrxpXQ6w3TpdPo\ndtOWcwG2z4MlneHXtyDrvL0xClGAJHohLlfta6H/B3DHt3BFF9N2IcWUOP6iO+xdCrqE2auEcCFJ\n9EI466puMHgV3LwYgpqatrOJsOFJWH6T6csXwkaS6IWwglLQeJDpzun5OvjXM+1J22HV3bDqXkiK\ntzdGUWVJohfCStWqQ6tHYdjP0HE8eNcw7X+sh2V9YcNfzNW+EC4kiV6IiuBTCzo9b0botHgIVDVA\nw94lZoTO5mmQkWJ3lKKKkEQvREWqcRXc8Cbc/SNcc5tpy86AX//pGKEzz/wsRAWSRC+EKwQ3g1s+\ngkEr4YpOpi0jGTa/AEuvh31fyggdUWEk0QvhSldfD4O/g5veh8DGpu3sIVj/BHx9M/yx0d74hEeS\nRC+EqykFTQbDPf+BHq+BX4hpP/krrLoLvhsOp2QmTmEdSfRC2MXLB1qPhPu2QIdx4OWodX84Cr66\nEX4YC2eP2Buj8AiS6IWwm08t6DzRjNBp/mD+CJ09n8LSrvDzy6YmvhCXSRK9EO4i4Gro/X9w10Zo\neItpyz4Pv8yBzztD/DuQfcHeGEWlJIleCHdTpwXc+inc/jXU62DaMk7BT5Phix6wf7lMeiLKRRK9\nEO6qfi8Yshb6LYRajUzbmQSIfhy+uQWO/GhndKISkUQvhDtTCpreCff+F66fAb51TPuJWPj2Tvju\nfji1294YhduTRC9EZeDlA21GwfAYaP80ePmZ9sPrYFlv2PgUpB21N0bhtiTRC1GZ+ARClymmaFqz\nBwBlnqj97RNY0hW2zIALZ+yOUrgZSfRCVEY168ONc+HuH6DBzaYt+xzEvWlq6OxYKCN0RB5J9EJU\nZnVawYDPYeAyCLnOtJ1Pgv8+D1/2hAPfyAgdIYleCI8Q2hvuXAd934GaDU1b6u/w/WOwYgAc/cne\n+IStnEv0St2LUjtQKgelOlsUkxDicqhqEH43DPsJur8MvsGm/fhWiLwD1j4IyXvsjVHYwtkr+njg\nLkBK7gnhLrx8oe1ouC8GrhtrfgY4+B181Qv+/Qyk/8/eGIVLOZfotd6F1r9ZFIsQwkq+QdD1BRi2\nGa4dTt4Ind0fmhE6W1+TETpVhOv66JUahVIxKBXDiRMuO6wQVV7NMOjzFty1HsL6mbasdNg2yxRN\n2/k+5GTaG6OoUKUneqWiUCq+mNeQch1J6wVo3RmtO1Ov3uXGK4S4XHXbwG1LYeBXULetaTt3Av7z\nLHzRE36PlBE6Hsq71C20vtkFcQghXCX0Rhj6Pez/yjxgdfYwpB6AqAi4ogt0mwpXdbM7SmEhGV4p\nRFWkqkH4vXDvT9BtGvgEmfbjW2Dl7bDuYTi9194YhWWcHV45FKUSgeuBb1FqjSVRCSFcw9sP2v3F\njNBp9xeo5mPaE76FL3vBj+Mh/Zi9MQqnOTvqZjlah6G1L1pfida3WhSXEMKV/ILNlf2wzRA+DDNC\nJxt2LXaM0HkdMs/aHaW4TNJ1I4TIV6sB9J1n+vBDbzRtWWmw7XWT8HcthpwsW0MU5SeJXghRVEg7\nMzrnti+gThvTdu646cr5shckrJIROpWIJHohRMnC+pqr+xv/BQGhpi1lH6x7CFYOgmNb7I1PlIkk\neiHEpVXzgmb3wbBN5klbn0DTfmwzrLgNoh6B0/vsjVFckiR6IUTZePub2jn3xZhaOrkjdH5fabpz\n/vOceQBLuB1J9EKIQrTWLF26lNTU1OI38KtjqmPe+xM0vdvxpizY+Z6Z9GTbLMhMc13AolSS6IUQ\nhSilSEtLY/jw4WRnZ5e8YeA10O8duDMK6t9g2jLTTLG0pV1N8bTiRujIzFcuJ4leiCrs7bffplGj\nRkVe06ZNY/Xq1axcubL0ndRrb2a4GrDEzHgF5iGrfz8DX/U25ZELjtCJesQUVRMuI4leiCps9OjR\nJCQkFHmNHDmSiIgI7rzzzrLtSClocBMMXQ83/hMCrjbtp/eYCU8iB8PxbSbhH1oLa0dA1rmK+2Ci\nEEn0QohCtNb069ePBQsWlP/N1byg2f0w7Gfo8neoXsu0/+8n+OYW+P5RQMMfP8DahyDrvKWxi+JJ\nohdCFKKUokePHlSvXv3yd+LtD+2fguEx0ObPUM2xr98LdAX9sd4UT8vOcC5gUSpJ9EKIIjp37kzb\ntm2d35FfXbh+uhmhE3ZT0fWJ38O6CEn2FUwSvRCikKysLOLj4+nQoYN1O02KhxOxxa87vA6iHpXR\nOBVIEr0QopCdO3eSkZFhXaJPTYBDa8C/HqCK3+bQGvh+pExpWEFKn2FKCFGlxMXFAViX6AMbmZE4\nYCYjPxlnRuCccLzSjpp1B1fB94/DTe/m9+kLS8gVvRAe4OTJk0yaNInWrVvj7+9PnTp16N27N998\n803eNnv37iUiIoLQ0FB8fHwIDw9n9uzZ6IuqUMbGmi6W9u3bWx+oTy3zcFX7p6D/B/DAdvPq/yG0\nfxoupJgKmVIK2VJyRS9EJRcfH0///v1JSUnh8ccfp127dhw7dozVq1eza9cuhgwZwtq1axk6dCih\noaGMGTOG4OBgIiMjGT9+PElJScyYMSNvf3FxcTRu3JjatWu75gMEXG1ejQaan3WOmfREWEZd/L+5\nS3zXWTMgxvXHFeISFi9eDEBERIStcZTH6dOnadu2LdWqVSM6OpqmTZsWWn/+/HmOHj1K27Zt6dCh\nA2vWrKFGjRp567t3705cXBynTp3Kaw8ODqZv374sW7bMpZ9FlMGnaisP6M7lfZt03QhRib366qsk\nJiby6aefFknyAH5+frzyyiukp6ezcOHCQkkeoE+fPmRkZHDw4EEAEhISOH36tLUjboTtJNELUUlp\nrfn444/p3r07PXv2LHabnJwcli9fTt++fWnevHmx+wAICAgA8vvnCyb6pUuX0qtXL2rWrEmjRo0s\n/hTCFSTRC1FJHT9+nCNHjtC9e/cSt0lMTCQ5OZlWrVoVuz4+Pp7g4GAaNGgAFJ/og4ODGTNmDNOn\nT7cweuFKcjNWiEoqJSUFMCULSpK7zsfHp8i6w4cPs27dOkaMGJG3XVxcHPXq1SM0NDRvu/79+wPw\n9ddfWxa7cC3nruiVegOldqPUryi1HKVcdJteCBEWFoaPjw9RUVFF6sZrrcnKyiIsLIzAwEA2btxY\naP25c+cYMWIEXl5eTJw4Ma89NjZW+uc9kLNdN+uANmjdDtgDTCxleyGERWrUqMHo0aPZvn07vXr1\n4s033+Sdd95h3LhxNGnShPT0dJRSTJkyhZiYGAYNGsT8+fOZOXMm7du3Z9OmTSxZsoTw8HAAkpKS\nSExMlETvgZzrutF6bYGfNgH3OLU/IUS5zJ49mxYtWrBgwQKmTp0KQOPGjXn44YcJDDSTeI8fPx6A\n+fPns3btWurWrZs3fLJ169Z5+6rQB6WErazso38UWFLiWqVGAaMA+LChhYcVoury8vLiiSee4Ikn\nnihxG6UUEyZMYMKECZfc180331zkKVnhGUpP9EpFAVcVs2YyWn/j2GYykAV8UuJ+tF4AmJkMvuss\nf5uEqCSys7PJzMwkMzMTrTXnz59HKYWvr6/doYkyKj3Ra33zJdcrFQEMAm6SywEhPM9HH33EI488\nkvezv78/11xzDQkJCfYFJcrF2VE3A4BngcFoLbP9CuGBIiIi0FoXekmSr1ycHXXzFlALWIdScSg1\n34KYhBBCWMjZUTfhFsUhhBCigkgJBCGE8HCS6IUQwsNJohdCCA8niV4IITycJHohhPBwkuiFEMLD\nSaIXQggPJ4leCCE8nCR6IYTwcJLohRDCw0miF0IIDyeJXgghPJwkeiGE8HCS6IUQwsNJohdCCA8n\niV4IITycJHohhPBwkuiFEMLDSaIXQggPJ4leCCE8nCR6IYTwcJLohRDCwzmX6JV6GaV+Rak4lFqL\nUvUtiksIIYRFnL2ifwOt26F1eyASeMGCmIQQQljIuUSvdWqBnwIA7dT+hBBCWE5p7WRuVmo68BCQ\nAvRF6xMlbDcKGAXAQtrgT7xzB3aB04RQm5N2h1EqidM6lSFGkDitVlnivEBzInSt8r6t9ESvVBRw\nVTFrJqP1NwW2mwj4ofWLpR9VxaB153JFageJ01qVIc7KECNInFbz8Di9S91C65vLuK9PgFVA6Yle\nCCGEyzg76ubaAj8NAXY7tT8hhBCWK/2K/tJeQ6nmQA5wEHiijO9b4ORxXUXitFZliLMyxAgSp9U8\nOk7nb8YKIYRwa/JkrBBCeDhJ9EII4eFck+iVegOldjvKJSxHqdolbDcApX5DqX0o9bxLYit8/HtR\nagdK5aBUyUOYlEpAqe2O0g8xLoww9/hljdO+86lUHZRah1J7HX8Gl7BdtuM8xqHUChfGd+lzo5Qv\nSi1xrN+MUo1cFlvhOEqLMwKlThQ4hyNtiPF9lDqOUsU/G6OUQqm5js/wK0p1dHGEuXGUFmcflEop\ncC7tedJfqQYotR6ldjr+nT9VzDblO6da64p/wS0avB3LMzXMLGYbLw37NTTR4KPhFw2tXBJffgwt\nNTTXsEFD50tsl6AhxKWxlTdOu88nvK7hecfy88X+zs26szacv9LPDTypYb5jebiGJW4aZ4SGt1we\nW+EYemvoqCG+hPUDNazWoDR017DZTePsoyHS1nNp4rhaQ0fHci0Ne4r5vZfrnLrmil7rtWid5fhp\nExBWzFZdgX1ofQCtLwCfY4Zsuo7Wu9D6N5ce83KULU67z+cQ4APH8gfAnS48dmnKcm4Kxv8lcBNK\nKRfGCPb/DstG643AqUtsMQT40JF1NgG1Uepq1wRXQOlxugetj6L1NsfyGWAXEHrRVuU6p3b00T8K\nrC6mPRQ4XODnRIp+OHehgbUotdVR2sEd2X0+r0Tro47l/wFXlrCdH0rFoNQmlHLVfwZlOTf525iL\nlBSgriuCKzYGo6Tf4d2Or+9folQD14RWLnb/XSyP61HqF5RajVKt7Q7G0WXYAdh80ZpynVNnx9EX\nDKj0UglKTQayME/R2qOsJR0urRda/4FSVwDrUGq342rBOtbEWbEuFWNBWmuUKmkc7zWOc9kEiEap\n7Wi93+JIPdlK4DO0zkCpP2O+hfSzOabKahvm7+NZlBoIfA1cW8p7Ko5SNYGvgKcpXECy3KxL9KWV\nSlAqAhgE3ITWxf2j/wMoeDUS5mizVtlLOlxqH384/jyOUssxX7GtTfTOx1nx5/NSMSp1DKWuRuuj\njq+Ux0vYR+65PIBSGzBXLxWd6MtybnK3SUQpbyAISKrguC5WepxaF4xpIfB6xYdVbq75t+2sgslU\n61UoNQ+lQtDa9cXOlKqOSfKfoPWyYrYo1zl11aibAcCzwGC0Ti9hqy3AtSjVGKV8gOGA60ZhlJVS\nAShVK28ZbgG3rMRp9/lcATzsWH4YKPotRKlglPJ1LIcAPYGdLoitLOemYPz3ANElXKBUpNLjLNwv\nOxjTn+tuVgAPOUaKdAdSCnTruQ+lrsq7D6NUV0x+dPV/7jhieA/YhdZvlrBV+c6pi+4i79NwWEOc\n45U7mqG+hlUX3Une4xhpMNmGu91DNSRqyNBwTMOaInGaERC/OF473DZOu88n1NXwvYa9GqI01HG0\nd9aw0LHcQ8N2x7ncruExF8ZX9NzASxoGO5b9NHzh+Lv7s4YmLv89ly3OVx1/D3/RsF5DCxti/EzD\nUQ2Zjr+Xj2l4QsMTjvVKw78cn2G7vtSINnvjHFPgXG7S0MOmOHtp0Bp+LZAzBzpzTqUEghBCeDh5\nMlYIITycJHohhPBwkuiFEMLDSaIXQggPJ4leCCE8nCR6IYTwcJLohRDCw/0/2fulk+BebEUAAAAA\nSUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Inverse of Matrix A: \n",
      "[[ 0.87758255  0.47942555]\n",
      " [-0.47942555  0.87758255]] \n",
      "\n",
      "Equals the transpose of Matrix A: \n",
      "[[ 0.87758255  0.47942555]\n",
      " [-0.47942555  0.87758255]]\n"
     ]
    }
   ],
   "source": [
    "# Lets use sine and cosine to create orthogonal matrix\n",
    "ortho_matrix_A = tf.Variable([[tf.cos(.5), -tf.sin(.5)], [tf.sin(.5), tf.cos(.5)]], name=\"matrixA\")\n",
    "print(\"Matrix A: \\n{}\\n\".format(ortho_matrix_A))\n",
    "\n",
    "# extract columns from the matrix to verify if they are orthogonal\n",
    "col_0 = tf.reshape(ortho_matrix_A[:, 0], [2, 1])\n",
    "col_1 = tf.reshape(ortho_matrix_A[:, 1], [2, 1])\n",
    "row_0 = tf.reshape(ortho_matrix_A[0, :], [2, 1])\n",
    "row_1 = tf.reshape(ortho_matrix_A[1, :], [2, 1])\n",
    "\n",
    "# Verifying if the columns are orthogonal\n",
    "ortho_column = tf.tensordot(tf.transpose(col_0), col_1, axes=2)\n",
    "print(\"Columns are orthogonal: {}\".format(ortho_column))\n",
    "plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'red'})\n",
    "origin = [0, 0]\n",
    "plt.xlim(-2, 2)\n",
    "plt.ylim(-3, 4)\n",
    "plt.axvline(x=0, color='grey', zorder=0)\n",
    "plt.axhline(y=0, color='grey', zorder=0)\n",
    "plt.text(0, 2, r'$\\vec{col_0}$', size=18)\n",
    "plt.text(0.5, -2, r'$\\vec{col_1}$', size=18)\n",
    "plt.quiver(*origin, col_0, col_1, color=['#FF9A13','#FF9A13'], scale=3)\n",
    "\n",
    "# Verifying if the rows are orthogonal\n",
    "ortho_row = tf.tensordot(tf.transpose(row_0), row_1, axes=2)\n",
    "print(\"Rows are orthogonal: {}\\n\".format(ortho_row))\n",
    "plt.text(-1, 2, r'$\\vec{row_0}$', size=18)\n",
    "plt.text(1, 0.5, r'$\\vec{row_1}$', size=18)\n",
    "plt.quiver(*origin, row_0, row_1, color=['r','r'], scale=3)\n",
    "plt.show()\n",
    "\n",
    "# inverse of matrix A\n",
    "ortho_inverse_A = tf.linalg.inv(ortho_matrix_A)\n",
    "\n",
    "# Transpose of matrix A\n",
    "ortho_transpose_A = tf.transpose(ortho_matrix_A)\n",
    "\n",
    "predictor = tf.reduce_all(tf.equal(ortho_inverse_A, ortho_transpose_A))\n",
    "def true_print(): print(\"Inverse of Matrix A: \\n{} \\n\\nEquals the transpose of Matrix A: \\n{}\".format(ortho_inverse_A, ortho_transpose_A))\n",
    "def false_print(): print(\"Inverse of Matrix A: \\n{} \\n\\nDoes not equal the transpose of Matrix A: \\n{}\".format(ortho_inverse_A, ortho_transpose_A))\n",
    "\n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "iqm-A3-mIIQ8"
   },
   "source": [
    "# 02.07 - Eigendecomposition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "C3sxNG4coJKI"
   },
   "source": [
    "We can represent a number, for example 12 as 12 = 2 x 2 x 3. The representation will change depending on whether we write it in base ten or in binary but the above representation will always be true and from that we can conclude that 12 is not divisible by 5 and that any integer multiple of 12 will be divisible by 3. \n",
    "\n",
    "Similarly, we can also decompose matrices in ways that show us information about their functional properties that is not obvious from the representation of the matrix as an array of elements. One of the most widely used kinds of matrix decomposition is called __eigendecomposition__, in which we decompose a matrix into a set of eigenvectors and eigenvalues.\n",
    "\n",
    "An __eigenvector__ of a square matrix $A$ is a nonzero vector $v$ such that multiplication by $A$ alters only the scale of $v$, in short this is a special vector that doesn't change the direction of the matrix when applied to it :\n",
    "\n",
    "$$\\color{Orange}{Av = \\lambda v \\tag{21}}$$\n",
    "\n",
    "The scale $\\lambda$ is known as the __eigenvalue__ corresponding to this eigenvector."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 473
    },
    "colab_type": "code",
    "id": "cepU9Pq-26hW",
    "outputId": "e235cb08-730f-41d6-a104-6783e825f393"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[5.450138 9.455662]\n",
      " [9.980919 9.223391]]\n",
      "\n",
      "\n",
      "Eigen Vectors: \n",
      "[[-0.76997876 -0.6380696 ]\n",
      " [ 0.6380696  -0.76997876]] \n",
      "\n",
      "Eigen Values: \n",
      "[-2.8208985 17.494429 ]\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Let's see how we can compute the eigen vectors and values from a matrix\n",
    "e_matrix_A = tf.random.uniform([2, 2], minval=3, maxval=10, dtype=tf.float32, name=\"matrixA\")\n",
    "print(\"Matrix A: \\n{}\\n\".format(e_matrix_A))\n",
    "\n",
    "# Calculating the eigen values and vectors using tf.linalg.eigh, if you only want the values you can use eigvalsh\n",
    "eigen_values_A, eigen_vectors_A = tf.linalg.eigh(e_matrix_A)\n",
    "print(\"Eigen Vectors: \\n{} \\n\\nEigen Values: \\n{}\\n\".format(eigen_vectors_A, eigen_values_A))\n",
    "\n",
    "# Now lets plot our Matrix with the Eigen vector and see how it looks\n",
    "Av = tf.tensordot(e_matrix_A, eigen_vectors_A, axes=0)\n",
    "vector_plot([tf.reshape(Av, [-1]), tf.reshape(eigen_vectors_A, [-1])], 10, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "xCepvxWtCz-6"
   },
   "source": [
    "If $v$ is an eigenvector of $A$, then so is any rescaled vector $sv$ for $s \\in \\mathbb{R}, s \\neq 0$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 269
    },
    "colab_type": "code",
    "id": "lZHzQadlC0Z_",
    "outputId": "c273214f-824f-45d2-b1c4-0da70300ee3e"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAY0AAAD8CAYAAACLrvgBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAGiJJREFUeJzt3XuUVOWd7vHvDxpQaby0jSAXo1Fg\nvEwEQfBC0k0gSDhJGDM4B3vFaBJXR0/MmpzRNaNxDuPoysVJNGtmaaQZ9ZgLGnESlVGMoukO8STK\nLaAo0qCQCCjIRaFtDDT9O3/Upi2aqu4Xqnbt2vJ81qrVtfd+e9ez3q7uh6p6qzB3R0REJESPpAOI\niEh6qDRERCSYSkNERIKpNEREJJhKQ0REgqk0REQkWHFKw+x+zLZgtjJrXxVmCzBbE309Ic/3XhmN\nWYPZlUXJIyIisSjWI40HgCmd9t0IPIf7MOC5aPtAZlXAvwDjgLHAv+QtFxERSVxxSsN9IbC9095p\nwE+i6z8B/ibHd14CLMB9O+47gAUcXD4iIlImKmI89wDc34quvw0MyDFmMPBm1vaGaN/BzOqBeoC2\nB44avfuYU4qXNCb72tvp2aO8XzZKQ0ZIR84PPviAnj0r6NUrzl+r4kjDfIJyFlu/vc1bqfP+hZyj\nNPdud8essM8rcZ8NzAbY/cgI73fZ6mIki1VTUxO1tbVJx+hSGjJCOnJ+vraWuro66uvrk47SrTTM\nJyhn0T1ofyr0FHFW42bMTgaIvm7JMWYjMDRre0i0T0REylCcpTEP2L8a6krg8RxjngYmY3ZC9AL4\n5GifiIiUoWItuX0I+AMwArMNmH0N+D7wGczWAJOibTAbg9m9ALhvB24DFkeXW6N9IiJShorzmob7\n5XmOTMwxdglwddb2/cD9RckhIiKxKv+X+0VEpGyoNEREJJhKQ0REgqk0REQkmEpDRESCqTRERCSY\nSkNERIKpNEREJJhKQ0REgqk0REQkmEpDRESCqTRERCSYSkNERIKpNEREJJhKQ0REgqk0REQkmEpD\nRESCqTRERCRYvKVhNgKz5VmXnZh9q9OYWszeyxozM9ZMIiJy2Irzf4Tn474aGAmAWU9gI/BojpG/\nw/1zsWYREZGClfLpqYnA67j/qYS3KSIiRVTK0pgBPJTn2IWYrcDsKczOLmEmERE5BObuJbgV6w1s\n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\njQAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Lets us multiply our eigen vector by a random value s and plot the above graph again to see the rescaling\n",
    "sv = tf.multiply(5, eigen_vectors_A)\n",
    "vector_plot([tf.reshape(Av, [-1]), tf.reshape(sv, [-1])], 10, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "gCZw_K6yC2pW"
   },
   "source": [
    "Suppose that a matrix $A$ has $n$ linearly independent eigenvectors $\\{v^{(1)}, \\cdots, v^{(n)}\\}$ with corresponding eigenvalues $\\{\\lambda_{(1)}, \\cdots, \\lambda_{(n)}\\}$. We may concatenate all the eigenvectors to form a matrix $V$ with one eigenvector per column: $V = [v^{(1)}, \\cdots, v^{(n)}]$. Likewise, we can concatenate the eigenvalues to form a vector $\\lambda = [\\lambda_{(1)}, \\cdots, \\lambda_{(n)}]^{\\top}$. The __eigendecomposition__ of $A$ is then given by\n",
    "\n",
    "$$\\color{Orange}{A = V diag(\\lambda)V^{-1} \\tag{22}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 238
    },
    "colab_type": "code",
    "id": "jWQc7HinC3Dw",
    "outputId": "912b01d6-7303-4e99-af68-82abc6dcab7d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Eigen Values of Matrix A: [0.8377223 7.1622777] \n",
      "\n",
      "Eigen Vector of Matrix A: \n",
      "[[-0.5847103   0.81124216]\n",
      " [ 0.81124216  0.5847103 ]]\n",
      "\n",
      "Diagonal of Lambda: \n",
      "[[0.8377223 0.       ]\n",
      " [0.        7.1622777]]\n",
      "\n",
      "The decomposition Matrix A: \n",
      "[[-3.3302479 -3.195419 ]\n",
      " [-4.786382  -2.7909322]]\n"
     ]
    }
   ],
   "source": [
    "# Creating a matrix A to find it's decomposition\n",
    "eig_matrix_A = tf.constant([[5, 1], [3, 3]], dtype=tf.float32)\n",
    "new_eigen_values_A, new_eigen_vectors_A = tf.linalg.eigh(eig_matrix_A)\n",
    "\n",
    "print(\"Eigen Values of Matrix A: {} \\n\\nEigen Vector of Matrix A: \\n{}\\n\".format(new_eigen_values_A, new_eigen_vectors_A))\n",
    "\n",
    "# calculate the diag(lamda)\n",
    "diag_lambda = tf.linalg.diag(new_eigen_values_A)\n",
    "print(\"Diagonal of Lambda: \\n{}\\n\".format(diag_lambda))\n",
    "\n",
    "# Find the eigendecomposition of matrix A\n",
    "decomp_A = tf.tensordot(tf.tensordot(eigen_vectors_A, diag_lambda, axes=1), tf.linalg.inv(new_eigen_vectors_A), axes=1)\n",
    "\n",
    "print(\"The decomposition Matrix A: \\n{}\".format(decomp_A))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "O5aixrcmC3pZ"
   },
   "source": [
    "Not every matrix can be decomposed into eigenvalues and eigenvectors. In some cases, the decomposition exists but involves complex rather than real numbers.\n",
    "\n",
    "In this book, we usually need to decompose only a specific class of matrices that have a simple decomposition. Specifically, every real symmetric matrix can be decomposed into an expression using only real-valued eigenvectors and eigenvalues:\n",
    "\n",
    "$$\\color{Orange}{A = Q \\Lambda Q^{\\top} \\tag{23}}$$\n",
    "\n",
    "where $Q$ is an orthogonal matrix composed of eigenvectors of $A$ and $\\Lambda$ is a diagonal matrix. The eigenvalue $\\Lambda_{i,i}$ is associated with the eigenvector in column *i* of $Q$, denoted as $Q_{:, i}$. Because $Q$ is an orthogonal matrix, we can think of $A$ as scaling space by $\\Lambda_i$ in direction $v^{(i)}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 374
    },
    "colab_type": "code",
    "id": "c057PJzaK07f",
    "outputId": "efe33e88-9306-4281-c28b-61de2c526c9a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Symmetric Matrix A: \n",
      "[[4.517448  3.3404353]\n",
      " [3.3404353 7.411926 ]]\n",
      "\n",
      "Matrix Q: \n",
      "[[-0.8359252 -0.5488433]\n",
      " [ 0.5488433 -0.8359252]]\n",
      "\n",
      "Matrix Lambda: \n",
      "[[2.3242188 0.       ]\n",
      " [0.        9.605155 ]]\n",
      "\n",
      "\n",
      "It WORKS. \n",
      "RHS: \n",
      "[[4.5174475 3.340435 ]\n",
      " [3.340435  7.4119253]] \n",
      "\n",
      "LHS: \n",
      "[[4.517448  3.3404353]\n",
      " [3.3404353 7.411926 ]]\n"
     ]
    }
   ],
   "source": [
    "# In section 2.6 we manually created a matrix to verify if it is symmetric, but what if we don't know the exact values and want to create a random symmetric matrix\n",
    "new_matrix_A = tf.Variable(tf.random.uniform([2,2], minval=1, maxval=10, dtype=tf.float32))\n",
    "\n",
    "# to create an upper triangular matrix from a square one\n",
    "X_upper = tf.linalg.band_part(new_matrix_A, 0, -1)\n",
    "sym_matrix_A = tf.multiply(0.5, (X_upper + tf.transpose(X_upper)))\n",
    "print(\"Symmetric Matrix A: \\n{}\\n\".format(sym_matrix_A))\n",
    "\n",
    "# create orthogonal matrix Q from eigen vectors of A\n",
    "eigen_values_Q, eigen_vectors_Q = tf.linalg.eigh(sym_matrix_A)\n",
    "print(\"Matrix Q: \\n{}\\n\".format(eigen_vectors_Q))\n",
    "\n",
    "# putting eigen values in a diagonal matrix\n",
    "new_diag_lambda = tf.linalg.diag(eigen_values_Q)\n",
    "print(\"Matrix Lambda: \\n{}\\n\".format(new_diag_lambda))\n",
    "\n",
    "sym_RHS = tf.tensordot(tf.tensordot(eigen_vectors_Q, new_diag_lambda, axes=1), tf.transpose(eigen_vectors_Q), axes=1)\n",
    "\n",
    "predictor = tf.reduce_all(tf.equal(tf.round(sym_RHS), tf.round(sym_matrix_A)))\n",
    "def true_print(): print(\"It WORKS. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(sym_RHS, sym_matrix_A))\n",
    "def false_print(): print(\"Condition FAILED. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(sym_RHS, sym_matrix_A))\n",
    "\n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "BcOmR2UyIYmW"
   },
   "source": [
    "The eigendecomposition of a matrix tells us many useful facts about the matrix. The matrix is singular if and only if any of the eigenvalues are zero. The eigendecomposition of a real symmetric matrix can also be used to optimize quadratic expressions of the form$f(x) = x^{\\top} Ax$ subject to $||x||_2 = 1$. \n",
    "\n",
    "The above equation can be solved as following, we know that if $x$ is an Eigenvector of $A$ and $\\lambda$ is the corresponding eigenvalue, then $Ax = \\lambda x$, therefore $f(x) = x^{\\top} Ax = x^{\\top} \\lambda x = x^{\\top} x \\lambda$ and since $||x||_2 = 1$ and $x^{\\top} x =1$, the above equation boils down to $f(x) = \\lambda$\n",
    "\n",
    "Whenever $x$ is equal to an eigenvector of $A, \\ f$ takes on the value of the corresponding eigenvalue and its minimum value within the constraint region is the minimum eigenvalue."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "zbuHw8qvHlmH"
   },
   "source": [
    "A matrix whose eigenvalues are all positive is called __positive definite__. A matrix whose eigenvalues are all positive or zero valued is called __positive semidefinite__. Likewise, if all eigenvalues are negative, the matrix is __negative definite__, and if all eigenvalues are negative or zero valued, it is __negative semidefinite__. Positive semidefinite matrices are interesting because they guarantee that $\\forall x, x^{\\top} Ax \\geq 0$. Positive definite matrices additionally guarantee that $x^{\\top} Ax = 0 \\implies x=0$.\n",
    "\n",
    "![Eigenvalue plots](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0207a.PNG)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "iSPdyYA5IKfn"
   },
   "source": [
    "# 02.08 - Singular Value Decomposition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "RDsv5fWQoJ0K"
   },
   "source": [
    "The __singular value decomposition (SVD)__ provides another way to factorize a matrix into  __singular vectors__ and __singular values__. The SVD enables us to discover some of the same kind of information as the eigendecomposition reveals, however, the SVD is more generally applicable. Every real matrix has a singular value decomposition, but the same is not true of the eigenvalue decomposition. SVD can be written as:\n",
    "\n",
    "$$\\color{Orange}{A = UDV^{\\top} \\tag{24}}$$\n",
    "\n",
    "Suppose $A$ is an *m x n* matrix, then $U$ is defined to be an *m x m* rotation matrix, $D$ to be an *m x n* matrix scaling & projecting matrix, and $V$ to be an *n x n* rotation matrix.\n",
    "\n",
    "Each of these matrices is defined to have a special structure. The matrices $U$ and $V$ are both defined to be orthogonal matrices $(U^{\\top} = U^{-1} \\ \\text{and} \\ V^{\\top} = V^{-1})$. The matrix $D$ is defined to be a diagonal matrix.\n",
    "\n",
    "The elements along the diagonal of $D$ are known as the __singular values__ of the matrix $A$. The columns of $U$ are known as the __left-singular vectors__. The columns of $V$ are known as the __right-singular vectors__.\n",
    "\n",
    "![Singular Value Decomposition](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0208a.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 289
    },
    "colab_type": "code",
    "id": "_JFGh7SKEcU9",
    "outputId": "693ccbfb-7998-403a-ff04-ae037c204d36"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[2. 3.]\n",
      " [4. 5.]\n",
      " [6. 7.]]\n",
      "\n",
      "Diagonal D: \n",
      "[11.782492    0.41578525] \n",
      "\n",
      "Matrix U: \n",
      "[[ 0.30449855 -0.86058956  0.40824753]\n",
      " [ 0.54340035 -0.19506174 -0.81649673]\n",
      " [ 0.78230214  0.47046405  0.40824872]] \n",
      "\n",
      "Matrix V^T: \n",
      "[[ 0.63453555  0.7728936 ]\n",
      " [ 0.7728936  -0.63453555]]\n"
     ]
    }
   ],
   "source": [
    "# mxn matrix A\n",
    "svd_matrix_A = tf.constant([[2, 3], [4, 5], [6, 7]], dtype=tf.float32)\n",
    "print(\"Matrix A: \\n{}\\n\".format(svd_matrix_A))\n",
    "\n",
    "# Using tf.linalg.svd to calculate the singular value decomposition where d: Matrix D, u: Matrix U and v: Matrix V\n",
    "d, u, v = tf.linalg.svd(svd_matrix_A, full_matrices=True, compute_uv=True)\n",
    "print(\"Diagonal D: \\n{} \\n\\nMatrix U: \\n{} \\n\\nMatrix V^T: \\n{}\".format(d, u, v))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 425
    },
    "colab_type": "code",
    "id": "qLZ6MRJLILAV",
    "outputId": "dbc522ea-93c9-424b-e13b-396170b416f0"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Orthogonal Matrix U: \n",
      "[[ 0.30449855 -0.86058956  0.40824753]\n",
      " [ 0.54340035 -0.19506174 -0.81649673]\n",
      " [ 0.78230214  0.47046405  0.40824872]]\n",
      "\n",
      "Diagonal Matrix D: \n",
      "[[11.782492    0.        ]\n",
      " [ 0.          0.41578525]\n",
      " [ 0.          0.        ]]\n",
      "\n",
      "Transpose Matrix V: \n",
      "[[ 0.63453555  0.7728936 ]\n",
      " [ 0.7728936  -0.63453555]]\n",
      "\n",
      "It WORKS. \n",
      "RHS: \n",
      "[[2. 3.]\n",
      " [4. 5.]\n",
      " [6. 7.]] \n",
      "\n",
      "LHS: \n",
      "[[2. 3.]\n",
      " [4. 5.]\n",
      " [6. 7.]]\n"
     ]
    }
   ],
   "source": [
    "# Lets see if we can bring back the original matrix from the values we have\n",
    "\n",
    "# mxm orthogonal matrix U\n",
    "svd_matrix_U = tf.constant([[0.30449855, -0.86058956, 0.40824753], [0.54340035, -0.19506174, -0.81649673], [0.78230214, 0.47046405, 0.40824872]])\n",
    "print(\"Orthogonal Matrix U: \\n{}\\n\".format(svd_matrix_U))\n",
    "\n",
    "# mxn diagonal matrix D\n",
    "svd_matrix_D = tf.constant([[11.782492, 0], [0, 0.41578525], [0, 0]], dtype=tf.float32)\n",
    "print(\"Diagonal Matrix D: \\n{}\\n\".format(svd_matrix_D))\n",
    "\n",
    "# nxn transpose of matrix V\n",
    "svd_matrix_V_trans = tf.constant([[0.63453555, 0.7728936], [0.7728936, -0.63453555]], dtype=tf.float32)\n",
    "print(\"Transpose Matrix V: \\n{}\\n\".format(svd_matrix_V_trans))\n",
    "\n",
    "# UDV(^T)\n",
    "svd_RHS = tf.tensordot(tf.tensordot(svd_matrix_U, svd_matrix_D, axes=1), svd_matrix_V_trans, axes=1)\n",
    "\n",
    "predictor = tf.reduce_all(tf.equal(tf.round(svd_RHS), svd_matrix_A))\n",
    "def true_print(): print(\"It WORKS. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(tf.round(svd_RHS), svd_matrix_A))\n",
    "def false_print(): print(\"Condition FAILED. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(tf.round(svd_RHS), svd_matrix_A))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "a5rObIJ_mfhA"
   },
   "source": [
    "Matrix $A$ can be seen as a linear transformation. This transformation can be decomposed into three sub-transformations: \n",
    "\n",
    "1. Rotation, \n",
    "2. Re-scaling and projecting, \n",
    "3. Rotation. \n",
    "\n",
    "These three steps correspond to the three matrices $U, D \\ \\text{and} \\ V$\n",
    "\n",
    "Let's see how these transformations are taking place in order"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 809
    },
    "colab_type": "code",
    "id": "LbnTw1v7mgOU",
    "outputId": "5f57fae5-fc3f-43c0-91da-c90314cbd2e0"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\nAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\noJRSyvX/AVVHrdbJ5llZAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\nX099EL121gAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Let's define a unit square\n",
    "svd_square = tf.constant([[0, 0, 1, 1],[0, 1, 1, 0]], dtype=tf.float32)\n",
    "\n",
    "# a new 2x2 matrix\n",
    "svd_new_matrix = tf.constant([[1, 1.5], [0, 1]])\n",
    "\n",
    "# SVD for the new matrix\n",
    "new_d, new_u, new_v = tf.linalg.svd(svd_new_matrix, full_matrices=True, compute_uv=True)\n",
    "\n",
    "# lets' change d into a diagonal matrix\n",
    "new_d_marix = tf.linalg.diag(new_d)\n",
    "\n",
    "# Rotation: V^T for a unit square\n",
    "plot_transform(svd_square, tf.tensordot(new_v, svd_square, axes=1), \"$Square$\", \"$V^T \\cdot Square$\", \"Rotation\", axis=[-0.5, 3.5 , -1.5, 1.5])\n",
    "plt.show()\n",
    "\n",
    "# Scaling and Projecting: DV^(T)\n",
    "plot_transform(tf.tensordot(new_v, svd_square, axes=1), tf.tensordot(new_d_marix, tf.tensordot(new_v, svd_square, axes=1), axes=1), \"$V^T \\cdot Square$\", \"$D \\cdot V^T \\cdot Square$\", \n",
    "               \"Scaling and Projecting\", axis=[-0.5, 3.5 , -1.5, 1.5])\n",
    "plt.show()\n",
    "\n",
    "# Second Rotation: UDV^(T)\n",
    "trans_1 = tf.tensordot(tf.tensordot(new_d_marix, new_v, axes=1), svd_square, axes=1)\n",
    "trans_2 = tf.tensordot(tf.tensordot(tf.tensordot(new_u, new_d_marix, axes=1), new_v, axes=1), svd_square, axes=1)\n",
    "plot_transform(trans_1, trans_2,\"$U \\cdot D \\cdot V^T \\cdot Square$\", \"$D \\cdot V^T \\cdot Square$\", \n",
    "                    \"Second Rotation\", color=['#1190FF', '#FF9A13'], axis=[-0.5, 3.5 , -1.5, 1.5])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "NdrBXo-PmQY1"
   },
   "source": [
    "The above sub transformations can be found for each matrix as follows:\n",
    "\n",
    "- $U$ corresponds to the eigenvectors of $A A^{\\top}$ \n",
    "- $V$ corresponds to the eigenvectors of $A^{\\top} A$\n",
    "- $D$ corresponds to the eigenvalues $A A^{\\top}$  or $A^{\\top} A$ which are the same.\n",
    "\n",
    "As an exercise try proving this is the case.\n",
    "\n",
    "Perhaps the most useful feature of the SVD is that we can use it to partially generalize matrix inversion to nonsquare matrices, as we will see in the next section."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "uzJR2fP5INLH"
   },
   "source": [
    "# 02.09 - The Moore-Penrose Pseudoinverse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1LQ48tySoKdT"
   },
   "source": [
    "Matrix inversion is not defined for matrices that are not square. Suppose we want to make a left-inverse $B$ of a matrix $A$ so that we can solve a linear equation $Ax = Y$ by left multiplying each side to obtain $x = By$.\n",
    "\n",
    "Depending on the structure of the problem, it may not be possible to design a unique mapping from $A$ to $B$.\n",
    "\n",
    "The __Moore-Penrose pseudoinverse__ enables use to make some headway in these cases. The pseudoinverse of $A$ is defined as a matrix:\n",
    "\n",
    "$$\\color{Orange}{A^+ = lim_{\\alpha \\rightarrow 0} (A^T A + \\alpha I)^{-1} A^{\\top} \\tag{25}}$$\n",
    "\n",
    "Practical algorithms for computing the pseudoinverse are based not on this definition, but rather on the formula:\n",
    "\n",
    "$$\\color{Orange}{A^+ = VD^+U^{\\top} \\tag{26}}$$\n",
    "\n",
    "where $U, D$ and $V$ are the singular decomposition of $A$ and the pseudoinverse of $D^+$ of a diagonal matrix $D$ is obtained by taking the reciprocal of its nonzero elements then taking the transpose of the resulting matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 374
    },
    "colab_type": "code",
    "id": "zN0ycGMvINu1",
    "outputId": "346aa29b-4b5f-4526-c4e1-b8ad7d7f1b71"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[6.1130576 2.7441363]\n",
      " [8.039717  5.8797803]\n",
      " [9.662358  8.468619 ]]\n",
      "\n",
      "Matrix U: \n",
      "[[ 0.3731766  -0.8532468   0.36429217]\n",
      " [ 0.56925267 -0.09947323 -0.8161228 ]\n",
      " [ 0.7325915   0.51193243  0.44859198]] \n",
      "\n",
      "Matrix V: \n",
      "[[ 0.79661715 -0.60448414]\n",
      " [ 0.60448414  0.79661715]]\n",
      "\n",
      "D plus: \n",
      "[[0.05716072 0.         0.        ]\n",
      " [0.         0.5653558  0.        ]]\n",
      "\n",
      "The Moore-Penrose pseudoinverse of Matrix A: \n",
      "[[-0.27460322 -0.0080738   0.2083109 ]\n",
      " [-0.39717284 -0.06446922  0.20524703]]\n"
     ]
    }
   ],
   "source": [
    "# Matrix A\n",
    "mpp_matrix_A = tf.random.uniform([3, 2], minval=1, maxval=10, dtype=tf.float32)\n",
    "print(\"Matrix A: \\n{}\\n\".format(mpp_matrix_A))\n",
    "\n",
    "# Singular Value decomposition of matrix A\n",
    "mpp_d, mpp_u, mpp_v = tf.linalg.svd(mpp_matrix_A, full_matrices=True, compute_uv=True)\n",
    "print(\"Matrix U: \\n{} \\n\\nMatrix V: \\n{}\\n\".format(mpp_u, mpp_v))\n",
    "\n",
    "# pseudo inverse of matrix D\n",
    "d_plus = tf.concat([tf.transpose(tf.linalg.diag(tf.math.reciprocal(mpp_d))), tf.zeros([2, 1])], axis=1)\n",
    "print(\"D plus: \\n{}\\n\".format(d_plus))\n",
    "\n",
    "# moore-penrose pseudoinverse of matrix A\n",
    "matrix_A_star = tf.matmul(tf.matmul(mpp_v, d_plus, transpose_a=True), mpp_u, transpose_b=True)\n",
    "\n",
    "print(\"The Moore-Penrose pseudoinverse of Matrix A: \\n{}\".format(matrix_A_star))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "QNJD5L__fwaL"
   },
   "source": [
    "When $A$ has more columns than rows, then solving a linear equation using the pseudoinverse provides one of the many possible solutions. Specifically, it provides the solution $x = A^+y$ with minimal Euclidean norm $||x||_2$ among all possible solutions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 153
    },
    "colab_type": "code",
    "id": "mRCWcuJnSB4H",
    "outputId": "b1791916-92ae-441b-c850-86d50c17cc83"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector y: \n",
      "[[2.]\n",
      " [3.]\n",
      " [4.]]\n",
      "\n",
      "Vector x: \n",
      "[[ 0.2598158 ]\n",
      " [-0.16676521]]\n"
     ]
    }
   ],
   "source": [
    "mpp_vector_y = tf.constant([[2], [3], [4]], dtype=tf.float32)\n",
    "print(\"Vector y: \\n{}\\n\".format(mpp_vector_y))\n",
    "\n",
    "mpp_vector_x = tf.matmul(matrix_A_star, mpp_vector_y)\n",
    "print(\"Vector x: \\n{}\".format(mpp_vector_x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "k9thS9HEfxLY"
   },
   "source": [
    "When $A$ has more rows than columns, it is possible for there to be no solution. In this case, using the pseudoinverse gives us the $x$ for which $Ax$ is as close as possible to $y$ in terms of Euclidean norm $||Ax - y||_2$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "KFn1dBPdIPtd"
   },
   "source": [
    "# 02.10 - The Trace Operator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "cdnbW6nLoK-6"
   },
   "source": [
    "The trace operator gives the sum of all the diagonal entries of a matrix:\n",
    "\n",
    "$$\\color{Orange}{Tr(A) = \\displaystyle\\sum_i A_{i,i} \\tag{27}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "-A7QQcJ0IQSl",
    "outputId": "3b0e3924-7234-4531-b478-9ab28a7aeb21"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Trace of Matrix A: \n",
      "[[1.0423017 9.2543545 9.264312 ]\n",
      " [8.952941  8.2056675 4.0400686]\n",
      " [8.285712  6.5746584 8.217355 ]] \n",
      "is: 17.46532440185547\n"
     ]
    }
   ],
   "source": [
    "# random 3x3 matrix A\n",
    "to_matrix_A = tf.random.uniform([3, 3], minval=0, maxval=10, dtype=tf.float32)\n",
    "\n",
    "# trace of matrix A using tf.linalg.trace\n",
    "trace_matrix_A = tf.linalg.trace(to_matrix_A)\n",
    "\n",
    "print(\"Trace of Matrix A: \\n{} \\nis: {}\".format(to_matrix_A, trace_matrix_A))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8UQt98ydgUeC"
   },
   "source": [
    "The trace operator is useful for a variety of reasons. Some operations that are difficult to specify without resorting to summation notation can be specified using matrix products and the trace operator. For example, the trace operator provides\n",
    "an alternative way of writing the Frobenius norm of a matrix:\n",
    "\n",
    "$$\\color{Orange}{||A||_F = \\sqrt{Tr(AA^{\\top})} \\tag{28}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "hvnl_PTNgVJK",
    "outputId": "91b384a5-f236-424e-bc2f-a42b0ad43bea"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "It WORKS. \n",
      "RHS: 22.71059799194336 \n",
      "LHS: 22.710599899291992\n"
     ]
    }
   ],
   "source": [
    "# Frobenius Norm of A\n",
    "frobenius_A = tf.norm(to_matrix_A)\n",
    "\n",
    "# sqrt(Tr(A times A^T))\n",
    "trace_rhs = tf.sqrt(tf.linalg.trace(tf.matmul(to_matrix_A, to_matrix_A, transpose_b=True)))\n",
    "\n",
    "predictor = tf.equal(tf.round(frobenius_A), tf.round(trace_rhs))\n",
    "def true_print(): print(\"It WORKS. \\nRHS: {} \\nLHS: {}\".format(frobenius_A, trace_rhs))\n",
    "def false_print(): print(\"Condition FAILED. \\nRHS: {} \\nLHS: {}\".format(frobenius_A, trace_rhs))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "dKDQCoRmgVve"
   },
   "source": [
    "Writing an expression in terms of the trace operator opens up opportunities to manipulate the expression using many useful identities. For example, the trace operator is invariant to the transpose operator:\n",
    "\n",
    "$$\\color{Orange}{Tr(A) = Tr(A^{\\top}) \\tag{29}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "p6L21KbGgWL4",
    "outputId": "4caac4e9-308e-49af-a735-a43535d51c14"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "It WORKS. \n",
      "RHS: 17.46532440185547 \n",
      "LHS: 17.46532440185547\n"
     ]
    }
   ],
   "source": [
    "# Transpose of Matrix A\n",
    "trans_matrix_A = tf.transpose(to_matrix_A)\n",
    "\n",
    "#Trace of the transpose Matrix A\n",
    "trace_trans_A = tf.linalg.trace(trans_matrix_A)\n",
    "\n",
    "predictor = tf.equal(trace_matrix_A, trace_trans_A)\n",
    "def true_print(): print(\"It WORKS. \\nRHS: {} \\nLHS: {}\".format(trace_matrix_A, trace_trans_A))\n",
    "def false_print(): print(\"Condition FAILED. \\nRHS: {} \\nLHS: {}\".format(trace_matrix_A, trace_trans_A))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "glbzaSZxg-qp"
   },
   "source": [
    "The trace of a square matrix composed of many factors is also invariant to moving the last factor into the first position, if the shapes of the corresponding matrices allow the resulting product to be defined:\n",
    "\n",
    "$$\\color{Orange}{Tr(ABC) = Tr(CAB) = TR(BCA) \\tag{30}}$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 85
    },
    "colab_type": "code",
    "id": "Cb2s2ehwg-Gi",
    "outputId": "c24e46d5-b887-4205-c9d1-8384b61e4160"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "It WORKS. \n",
      "ABC: 5854.24609375 \n",
      "CAB: 5854.24609375 \n",
      "BCA: 5854.24609375\n"
     ]
    }
   ],
   "source": [
    "# random 3x3 matrix B and matrix C\n",
    "to_matrix_B = tf.random.uniform([3, 3], minval=0, maxval=10, dtype=tf.float32)\n",
    "to_matrix_C = tf.random.uniform([3, 3], minval=0, maxval=10, dtype=tf.float32)\n",
    "\n",
    "# ABC\n",
    "abc = tf.tensordot((tf.tensordot(to_matrix_A, to_matrix_B, axes=1)), to_matrix_C, axes=1)\n",
    "\n",
    "# CAB\n",
    "cab = tf.tensordot((tf.tensordot(to_matrix_C, to_matrix_A, axes=1)), to_matrix_B, axes=1)\n",
    "\n",
    "# BCA\n",
    "bca = tf.tensordot((tf.tensordot(to_matrix_B, to_matrix_C, axes=1)), to_matrix_A, axes=1)\n",
    "\n",
    "# trace of matrix ABC, CAB and matrix BCA\n",
    "trace_matrix_abc = tf.linalg.trace(abc)\n",
    "trace_matrix_cab = tf.linalg.trace(cab)\n",
    "trace_matrix_bca = tf.linalg.trace(bca)\n",
    "\n",
    "predictor = tf.equal(tf.round(trace_matrix_abc), tf.round(trace_matrix_cab)) and tf.equal(tf.round(trace_matrix_cab), tf.round(trace_matrix_bca))\n",
    "def true_print(): print(\"It WORKS. \\nABC: {} \\nCAB: {} \\nBCA: {}\".format(trace_matrix_abc, trace_matrix_cab, trace_matrix_bca))\n",
    "def false_print(): print(\"Condition FAILED. \\nABC: {} \\nCAB: {} \\nBCA: {}\".format(trace_matrix_abc, trace_matrix_cab, trace_matrix_bca))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "doWjKavMhsUa"
   },
   "source": [
    "This invariance to cyclic permutation holds even if the resulting product has a different shape. For example, for $A \\in \\mathbb{R}^{m \\times n}$ and $B \\in \\mathbb{R}^{n \\times m}$, we have $Tr(AB) = Tr(BA)$ even though $AB \\in \\mathbb{R}^{m \\times m}$ and $BA \\in \\mathbb{R}^{n \\times n}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 221
    },
    "colab_type": "code",
    "id": "G95Dx1kniE34",
    "outputId": "df9f1385-42fd-4aaa-ca04-ac0dfda04255"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 3x2 Matrix A: \n",
      "[[1.4351392  2.850753  ]\n",
      " [3.6514747  7.5348463 ]\n",
      " [4.881668   0.43383718]]\n",
      "\n",
      " 3x2 Matrix B: \n",
      "[[1.7338943 9.986754  8.326568 ]\n",
      " [6.2179327 3.0207145 8.137293 ]]\n",
      "\n",
      "It WORKS. \n",
      "AB: 123.61897277832031 \n",
      "BA: 123.61897277832031\n"
     ]
    }
   ],
   "source": [
    "# mxn matrix A\n",
    "to_new_matrix_A = tf.random.uniform([3, 2], minval=0, maxval=10, dtype=tf.float32)\n",
    "print(\" 3x2 Matrix A: \\n{}\\n\".format(to_new_matrix_A))\n",
    "\n",
    "# mxn matrix B\n",
    "to_new_matrix_B = tf.random.uniform([2, 3], minval=0, maxval=10, dtype=tf.float32)\n",
    "print(\" 3x2 Matrix B: \\n{}\\n\".format(to_new_matrix_B))\n",
    "\n",
    "# trace of matrix AB and BA\n",
    "ab = tf.linalg.trace(tf.matmul(to_new_matrix_A, to_new_matrix_B))\n",
    "ba = tf.linalg.trace(tf.matmul(to_new_matrix_B, to_new_matrix_A))\n",
    "\n",
    "predictor = tf.equal(tf.round(ab), tf.round(ba))\n",
    "def true_print(): print(\"It WORKS. \\nAB: {} \\nBA: {}\".format(ab, ba))\n",
    "def false_print(): print(\"Condition FAILED. \\nAB: {} \\nBA: {}\".format(ab, ba))\n",
    "    \n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "CmXNT78kISsk"
   },
   "source": [
    "# 02.11 - The Determinant"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "3QqfXTzDoLt3"
   },
   "source": [
    "The determinant of a square matrix, denoted det($A$), is a function that maps matrices to real scalars. You can calculate the determinant of a  2 x 2 matrix as:\n",
    "\n",
    "![Determinant for 2by2 Matrix](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0211a.png)\n",
    "\n",
    "For a 3 x 3 matrix:\n",
    "\n",
    "![Determinant for 3by3 Matrix](https://raw.githubusercontent.com/adhiraiyan/DeepLearningWithTF2.0/master/notebooks/figures/fig0211b.png)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 337
    },
    "colab_type": "code",
    "id": "IecEWEBfITKm",
    "outputId": "0e5df399-ffcb-4f1f-d699-2a7055644596"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix A: \n",
      "[[3. 1.]\n",
      " [0. 3.]] \n",
      "Determinant of Matrix A: 9.0\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXYAAAD8CAYAAABjAo9vAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAD9VJREFUeJzt3X1sXeV9wPHvL05CCA4E4xBDHJFW\ndF0pIEoMbaEvFxgso6hIU6UxDzqGJiNtSFSjQqNUG900Fa1ayyaQqsImTVujbFKpWiHK6/CkTQvC\nDu+EpKy8QyAQAnFDAk6e/XEcrs1yY+Nzcs/N4+9HQvG9Pj73p0eXb07OPfc6UkpIkvIxr+4BJEnV\nMuySlBnDLkmZMeySlBnDLkmZMeySlBnDLkmZMeySlBnDLkmZmV/Lo/6kN3HEqloeep+xsV/T3X1E\nrTN0CteisGnTJpYuPZrly4+te5SO4POiqWPWYtvoGwymZdNtVk/Yj1gFa0Zqeeh9RoaHaTQatc7Q\nKVyLwpU3NhgcHGRozVDdo3QEnxdNHbMWa+P5mWzmqRhJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TM\nGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMVBf2iC4iHibijsr2\nKUn6yKo8Yr8a2Fjh/iRJs1BN2CP6ga8At1WyP0nSrFV1xH4TcC2wt6L9SZJmKVJKJfcQFwEXktKf\nENEAvklKF+1nuyFgCGDXrctXrz9xXbnHLWlsbIzu7u5aZ+gUrkVh8+bN9PT00NvbW/coHcHnRVOn\nrEXjlXNGGUwD021XRdi/C1wGjAOLgCOB20np0pY/c9dAYs1IucctabhTfut4B3AtCo1Gg8HBQYaG\nhuoepSP4vGjqmLVYGzMKe/lTMSldR0r9pLQKuAT4jwNGXZJ0UHkduyRlZn6le0tpGBiudJ+SpI/E\nI3ZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJyoxhl6TMGHZJ\nyoxhl6TMGHZJyoxhl6TMGHZpipK/A1jqAIZdmuSow/bUPYJUmmGXJvQv2c3Ri8brHkMqzbBLE85a\nsYOFXQm2P1P3KFIphl2acFb/O8UXL9xT7yBSSYZdAti9nVOW7Sy+fuHuemeRSjLsEsCL99O17/+G\n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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# calculate det of a matrix\n",
    "det_matrix_A = tf.constant([[3,1], [0,3]], dtype=tf.float32)\n",
    "det_A = tf.linalg.det(det_matrix_A)\n",
    "print(\"Matrix A: \\n{} \\nDeterminant of Matrix A: {}\".format(det_matrix_A, det_A))\n",
    "\n",
    "vector_plot(det_matrix_A, 5, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "s-VO9GT_jZHT"
   },
   "source": [
    "The determinant is equal to the product of all the eigenvalues of the matrix. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 119
    },
    "colab_type": "code",
    "id": "oLilAthsjaJe",
    "outputId": "ac5afa9d-0b9c-4ff9-a08d-f458dc9fb414"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "It WORKS. \n",
      "RHS: \n",
      "9.0 \n",
      "\n",
      "LHS: \n",
      "9.0\n"
     ]
    }
   ],
   "source": [
    "# Let's find the eigen values of matrix A\n",
    "d_eigen_values = tf.linalg.eigvalsh(det_matrix_A)\n",
    "eigvalsh_product = tf.multiply(d_eigen_values[0], d_eigen_values[1])\n",
    "\n",
    "# lets validate if the product of the eigen values is equal to the determinant\n",
    "predictor = tf.equal(eigvalsh_product, det_A)\n",
    "def true_print(): print(\"It WORKS. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(eigvalsh_product, det_A))\n",
    "def false_print(): print(\"Condition FAILED. \\nRHS: \\n{} \\n\\nLHS: \\n{}\".format(eigvalsh_product, det_A))\n",
    "\n",
    "tf.cond(predictor, true_print, false_print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "j6LjT0fAjf8d"
   },
   "source": [
    "The absolute value of the determinant can be thought of\n",
    "as a measure of how much multiplication by the matrix expands or contracts space. If the determinant is 0, then space is contracted completely along at least one dimension, causing it to lose all its volume. If the determinant is 1, then the transformation preserves volume."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 269
    },
    "colab_type": "code",
    "id": "13m1tDpovLzh",
    "outputId": "de0bc08d-562c-45b1-b0e4-40d7ea307f25"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\npeXiG6ZuvgAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# If you see the following plot, you can see the vectors are expanded\n",
    "\n",
    "vector_plot(tf.multiply(tf.abs(det_A), det_matrix_A), 50, 50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "QV2My3mJIVMz"
   },
   "source": [
    "# 02.12 - Example: Principal Components Analysis"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "25O1Y6aHoMJ5"
   },
   "source": [
    "PCA is a complexity reduction technique that tries to reduce a set of variables down to a smaller set of components that represent most of the information in the variables. This can be thought of as for a collection of data points applying lossy compression, meaning storing the points in a way that require less memory by trading some precision. At a conceptual level, PCA works by identifying sets of variables that share variance, and creating a component to represent that variance.\n",
    "\n",
    "Earlier, when we were doing transpose or the matrix inverse, we relied on using Tensorflow's built in functions but for PCA, there is no such function, except one in the Tensorflow Extended (tft).\n",
    "\n",
    "There are multiple ways you can implement a PCA in Tensorflow but since this algorithm is such an important one in the machine learning world, we will take the long route.\n",
    "\n",
    "The reason for having PCA under Linear Algebra is to show that PCA could be implemented using the theorems we studied in this Chapter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 269
    },
    "colab_type": "code",
    "id": "nlY8luDeIV53",
    "outputId": "311f0d21-a88b-4da1-a09f-c4466232288d"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\nHO80AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# To start working with PCA, let's start by creating a 2D data set\n",
    "\n",
    "x_data = tf.multiply(5, tf.random.uniform([100], minval=0, maxval=100, dtype = tf.float32, seed = 0))\n",
    "y_data = tf.multiply(2, x_data) + 1 + tf.random.uniform([100], minval=0, maxval=100, dtype = tf.float32, seed = 0)\n",
    "\n",
    "X = tf.stack([x_data, y_data], axis=1)\n",
    "\n",
    "plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'red'})\n",
    "plt.plot(X[:,0], X[:,1], '+', color='b')\n",
    "plt.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "DSc6vX9hvSM-"
   },
   "source": [
    "We start by standardizing the data. Even though the data we created are on the same scales, its always a good practice to start by standardizing the data because most of the time the data you will be working with will be in different scales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 269
    },
    "colab_type": "code",
    "id": "pM7kT2EptyQ6",
    "outputId": "41755381-dd27-4b0b-882a-7541a048ca12"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def normalize(data):\n",
    "    # creates a copy of data\n",
    "    X = tf.identity(data)\n",
    "    # calculates the mean\n",
    "    X -=tf.reduce_mean(data, axis=0)\n",
    "    return X\n",
    "\n",
    "normalized_data = normalize(X)\n",
    "plt.plot(normalized_data[:,0], normalized_data[:,1], '+', color='b')\n",
    "plt.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AEH2BAlMjlu_"
   },
   "source": [
    "Recall that PCA can be thought of as applying lossy compression to a collection of $x$ data points. The way we can minimize the loss of precision is by finding some decoding function $f(x) \\approx c$ where $c$ will be the corresponding vector. \n",
    "\n",
    "PCA is defined by our choice of this decoding function. Specifically, to make the decoder very simple, we chose to use matrix multiplication to map $c$ and define $g(c) = Dc$. Our goal is to minimize the distance between the input point $x$ to its reconstruction and to do that we use $L^2$ norm. Which boils down to our encoding function $c = D^{\\top}x$.\n",
    "\n",
    "Finally, to reconstruct the PCA we use the same matrix $D$ to decode all the points and to solve this optimization problem, we use eigendecomposition.\n",
    "\n",
    "Please note that the following equation is the final version of a lot of matrix transformations. I don't provide the derivatives because the goal is to focus on the mathematical implementation, rather than the derivation. But for the curious, You can read about the derivation in [Chapter 2 Section 11](https://www.deeplearningbook.org/contents/linear_algebra.html).\n",
    "\n",
    "$$\\color{Orange}{d^* = argmax_d \\ Tr(d^{\\top} X^{\\top} Xd) \\ \\text{subject to} \\ dd^{\\top} = 1 \\tag{31}}$$\n",
    "\n",
    "To find $d$ we can calculate the eigenvectors $X^{\\top} X$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "EO_rGA_tjmNG",
    "outputId": "d3ed19e1-685d-481c-b924-8243d095757d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Eigen Vectors: \n",
      "[[-0.8908606  -0.45427683]\n",
      " [ 0.45427683 -0.8908606 ]] \n",
      "Eigen Values: \n",
      "[   16500.715 11025234.   ]\n"
     ]
    }
   ],
   "source": [
    "# Finding the Eigne Values and Vectors for the data\n",
    "eigen_values, eigen_vectors = tf.linalg.eigh(tf.tensordot(tf.transpose(normalized_data), normalized_data, axes=1))\n",
    "\n",
    "print(\"Eigen Vectors: \\n{} \\nEigen Values: \\n{}\".format(eigen_vectors, eigen_values))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "viN-QXaejoNt"
   },
   "source": [
    "The eigenvectors (principal components) determine the directions of the new feature space, and the eigenvalues determine their magnitude.\n",
    "\n",
    "Now, let's use these Eigenvectors to rotate our data. The goal of the rotation is to end up with a new coordinate system where data is uncorrelated and thus where the basis axes gather all the variance. Thereby reducing the dimension.\n",
    "\n",
    "Recall our encoding function $c = D^{\\top} x$, where $D$ is the matrix containing the eigenvectors that we have calculated before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 269
    },
    "colab_type": "code",
    "id": "AD2p9v5rEM4S",
    "outputId": "db84dceb-f9d6-4a1b-a31c-355f8c2108d7"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "X_new = tf.tensordot(tf.transpose(eigen_vectors), tf.transpose(normalized_data), axes=1)\n",
    "\n",
    "plt.plot(X_new[0, :], X_new[1, :], '+', color='b')\n",
    "plt.xlim(-500, 500)\n",
    "plt.ylim(-700, 700)\n",
    "plt.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AnxwVvwNylWT"
   },
   "source": [
    "That is the transformed data and that's it folks for our chapter on Linear Algebra 😉."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "a_mkeFu_t1hC"
   },
   "source": [
    "# 💫 Congratulations\n",
    "\n",
    "You have successfully completed Chapter 2 Linear algebra of [Deep Learning with Tensorflow 2.0](https://www.adhiraiyan.org/DeepLearningWithTensorflow.html). To recap, we went through the following concepts:\n",
    "\n",
    "- Scalars, Vectors, Matrices and Tensors\n",
    "- Multiplying Matrices and Vectors\n",
    "- Identity and Inverse Matrices\n",
    "- Linear Dependence and Span\n",
    "- Norms\n",
    "- Special Kinds of Matrices and Vectors\n",
    "- Eigendecomposition\n",
    "- Singular Value Decomposition\n",
    "- The Moore-Penrose Pseudoinverse\n",
    "- The Trace Operator\n",
    "- The Determinant\n",
    "- Example: Principal Components Analysis\n",
    "\n",
    "We covered a lot of content in one notebook, like I mentioned in the begining, this is not meant to be an absolute beginner or a comprehensive chapter on Linear Algebra, our focus is Deep Learning with Tensorflow, so we only went through the material we need to understand Deep Learning. \n",
    "\n",
    "I tried to minimize the mathematics and focus more on the implementation side but if you like to study linear algebra on all of it's glory, or just want to read more about few sections take a look at [Linear Algebra by Jim Hefferon](http://joshua.smcvt.edu/linearalgebra/book.pdf) or [A First Course in Linear Algebra by Robert A. Beezer](http://linear.ups.edu/download/fcla-3.40-tablet.pdf). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!--NAVIGATION-->\n",
    "< [01.00 - Preface](01.00-Introduction.ipynb) | [Contents](Index.ipynb) | [03.00 - Probability and Information Theory](03.00-Probability-and-Information-Theory.ipynb) >\n",
    "\n",
    "<a href=\"https://colab.research.google.com/github/adhiraiyan/DeepLearningWithTF2.0/blob/master/notebooks/02.00-Linear-Algebra.ipynb\"><img align=\"left\" src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open in Colab\" title=\"Open and Execute in Google Colaboratory\"></a>\n"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "02.00-Linear-Algebra.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
